<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/%E6%AD%A6%E6%B1%8932x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/%E6%AD%A6%E6%B1%8916x16.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib1/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib1/pace/pace-theme-center-simple.min.css">
  <script src="/lib1/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"example.com","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="基础指南基础类型 TypeScript支持与JavaScript几乎相同的数据类型，此外还提供了实用的枚举类型方便我们使用。 布尔值： 最基本的数据类型就是简单的true&#x2F;false值，在JavaScript和TypeScript里叫做boolean（其它语言中也一样）。 1let isDone: boolean &#x3D; false;  数字： 和JavaScript一样，TypeScript里的所有">
<meta property="og:type" content="article">
<meta property="og:title" content="typeScript初学">
<meta property="og:url" content="http://example.com/2021/12/01/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88%E5%88%9D%E5%AD%A6/typeScript%E5%88%9D%E5%AD%A6/index.html">
<meta property="og:site_name" content="Technological Blog">
<meta property="og:description" content="基础指南基础类型 TypeScript支持与JavaScript几乎相同的数据类型，此外还提供了实用的枚举类型方便我们使用。 布尔值： 最基本的数据类型就是简单的true&#x2F;false值，在JavaScript和TypeScript里叫做boolean（其它语言中也一样）。 1let isDone: boolean &#x3D; false;  数字： 和JavaScript一样，TypeScript里的所有">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2021-12-01T04:18:39.000Z">
<meta property="article:modified_time" content="2021-11-29T03:18:38.565Z">
<meta property="article:author" content="Li Yudong">
<meta property="article:tag" content="公司技术栈">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="http://example.com/2021/12/01/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88%E5%88%9D%E5%AD%A6/typeScript%E5%88%9D%E5%AD%A6/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>typeScript初学 | Technological Blog</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">Technological Blog</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">IT小白的成长之旅</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="tags fa-fw"></i>标签<span class="badge">18</span></a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="th fa-fw"></i>分类<span class="badge">14</span></a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="archive fa-fw"></i>归档<span class="badge">95</span></a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

  <a href="https://github.com/tiarmor1" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/12/01/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88%E5%88%9D%E5%AD%A6/typeScript%E5%88%9D%E5%AD%A6/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          typeScript初学
        </h1>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-12-01 12:18:39" itemprop="dateCreated datePublished" datetime="2021-12-01T12:18:39+08:00">2021-12-01</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-29 11:18:38" itemprop="dateModified" datetime="2021-11-29T11:18:38+08:00">2021-11-29</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88/" itemprop="url" rel="index"><span itemprop="name">盒马技术栈</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
        <h1 id="基础指南"><a href="#基础指南" class="headerlink" title="基础指南"></a>基础指南</h1><h2 id="基础类型"><a href="#基础类型" class="headerlink" title="基础类型"></a>基础类型</h2><p> TypeScript支持与JavaScript几乎相同的数据类型，此外还提供了实用的枚举类型方便我们使用。</p>
<p>布尔值：</p>
<p>最基本的数据类型就是简单的true/false值，在JavaScript和TypeScript里叫做<code>boolean</code>（其它语言中也一样）。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> isDone: <span class="built_in">boolean</span> = <span class="literal">false</span>;</span><br></pre></td></tr></table></figure>

<p>数字：</p>
<p>和JavaScript一样，TypeScript里的所有数字都是浮点数。 这些浮点数的类型是 <code>number</code>。 除了支持十进制和十六进制字面量，TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> decLiteral: <span class="built_in">number</span> = <span class="number">6</span>;</span><br><span class="line"><span class="keyword">let</span> hexLiteral: <span class="built_in">number</span> = <span class="number">0xf00d</span>;</span><br><span class="line"><span class="keyword">let</span> binaryLiteral: <span class="built_in">number</span> = <span class="number">0b1010</span>;</span><br><span class="line"><span class="keyword">let</span> octalLiteral: <span class="built_in">number</span> = <span class="number">0o744</span>;</span><br></pre></td></tr></table></figure>

<p>字符串：</p>
<p>JavaScript程序的另一项基本操作是处理网页或服务器端的文本数据。 像其它语言里一样，我们使用 <code>string</code>表示文本数据类型。 和JavaScript一样，可以使用双引号（ <code>&quot;</code>）或单引号（<code>&#39;</code>）表示字符串。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> name: <span class="built_in">string</span> = <span class="string">"bob"</span>;</span><br><span class="line">name = <span class="string">"smith"</span>;</span><br></pre></td></tr></table></figure>

<p>你还可以使用<em>模版字符串</em>，它可以定义多行文本和内嵌表达式。 这种字符串是被反引号包围（ ```），并且以<code>${ expr }</code>这种形式嵌入表达式</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> name: <span class="built_in">string</span> = <span class="string">`Gene`</span>;</span><br><span class="line"><span class="keyword">let</span> age: <span class="built_in">number</span> = <span class="number">37</span>;</span><br><span class="line"><span class="keyword">let</span> sentence: <span class="built_in">string</span> = <span class="string">`Hello, my name is <span class="subst">$&#123; name &#125;</span>.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">I'll be <span class="subst">$&#123; age + <span class="number">1</span> &#125;</span> years old next month.`</span>;</span><br></pre></td></tr></table></figure>

<p>这与下面定义<code>sentence</code>的方式效果相同：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> sentence: <span class="built_in">string</span> = <span class="string">"Hello, my name is "</span> + name + <span class="string">".\n\n"</span> +</span><br><span class="line">    <span class="string">"I'll be "</span> + (age + <span class="number">1</span>) + <span class="string">" years old next month."</span>;</span><br></pre></td></tr></table></figure>

<p>数组：</p>
<p>TypeScript像JavaScript一样可以操作数组元素。 有两种方式可以定义数组。 第一种，可以在元素类型后面接上 <code>[]</code>，表示由此类型元素组成的一个数组：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> list: <span class="built_in">number</span>[] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br></pre></td></tr></table></figure>

<p>第二种方式是使用数组泛型，<code>Array&lt;元素类型&gt;</code>：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> list: <span class="built_in">Array</span>&lt;<span class="built_in">number</span>&gt; = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br></pre></td></tr></table></figure>

<h3 id="元组-Tuple"><a href="#元组-Tuple" class="headerlink" title="元组 Tuple"></a>元组 Tuple</h3><p>元组类型允许表示一个已知元素数量和类型的数组，各元素的类型不必相同。 比如，你可以定义一对值分别为 <code>string</code>和<code>number</code>类型的元组。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Declare a tuple type</span></span><br><span class="line"><span class="keyword">let</span> x: [<span class="built_in">string</span>, <span class="built_in">number</span>];</span><br><span class="line"><span class="comment">// Initialize it</span></span><br><span class="line">x = [<span class="string">'hello'</span>, <span class="number">10</span>]; <span class="comment">// OK</span></span><br><span class="line"><span class="comment">// Initialize it incorrectly</span></span><br><span class="line">x = [<span class="number">10</span>, <span class="string">'hello'</span>]; <span class="comment">// Error</span></span><br></pre></td></tr></table></figure>

<p>当访问一个已知索引的元素，会得到正确的类型：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">console</span>.log(x[<span class="number">0</span>].substr(<span class="number">1</span>)); <span class="comment">// OK</span></span><br><span class="line"><span class="built_in">console</span>.log(x[<span class="number">1</span>].substr(<span class="number">1</span>)); <span class="comment">// Error, 'number' does not have 'substr'</span></span><br></pre></td></tr></table></figure>

<p>当访问一个越界的元素，会使用联合类型替代：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">x[<span class="number">3</span>] = <span class="string">'world'</span>; <span class="comment">// OK, 字符串可以赋值给(string | number)类型</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(x[<span class="number">5</span>].toString()); <span class="comment">// OK, 'string' 和 'number' 都有 toString</span></span><br><span class="line"></span><br><span class="line">x[<span class="number">6</span>] = <span class="literal">true</span>; <span class="comment">// Error, 布尔不是(string | number)类型</span></span><br></pre></td></tr></table></figure>

<p>联合类型是高级主题，我们会在以后的章节里讨论它。</p>
<h3 id="枚举"><a href="#枚举" class="headerlink" title="枚举"></a>枚举</h3><p><code>enum</code>类型是对JavaScript标准数据类型的一个补充。 像C#等其它语言一样，使用枚举类型可以为一组数值赋予友好的名字。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> Color &#123;Red, Green, Blue&#125;</span><br><span class="line"><span class="keyword">let</span> c: Color = Color.Green;</span><br></pre></td></tr></table></figure>

<p>默认情况下，从<code>0</code>开始为元素编号。 你也可以手动的指定成员的数值。 例如，我们将上面的例子改成从 <code>1</code>开始编号：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> Color &#123;Red = <span class="number">1</span>, Green, Blue&#125;</span><br><span class="line"><span class="keyword">let</span> c: Color = Color.Green;</span><br></pre></td></tr></table></figure>

<p>或者，全部都采用手动赋值：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> Color &#123;Red = <span class="number">1</span>, Green = <span class="number">2</span>, Blue = <span class="number">4</span>&#125;</span><br><span class="line"><span class="keyword">let</span> c: Color = Color.Green;</span><br></pre></td></tr></table></figure>

<p>枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如，我们知道数值为2，但是不确定它映射到Color里的哪个名字，我们可以查找相应的名字：枚举类型其实是一个双向的映射，可由Enum.name映射至值，也可由Enum[value]映射至名字。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> Color &#123;Red = <span class="number">1</span>, Green, Blue&#125;</span><br><span class="line"><span class="keyword">let</span> colorName: <span class="built_in">string</span> = Color[<span class="number">2</span>];</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(colorName);  <span class="comment">// 显示'Green'因为上面代码里它的值是2</span></span><br></pre></td></tr></table></figure>

<h3 id="Any"><a href="#Any" class="headerlink" title="Any"></a>Any</h3><p>有时候，我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容，比如来自用户输入或第三方代码库。 这种情况下，我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 <code>any</code>类型来标记这些变量：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> notSure: <span class="built_in">any</span> = <span class="number">4</span>;</span><br><span class="line">notSure = <span class="string">"maybe a string instead"</span>;</span><br><span class="line">notSure = <span class="literal">false</span>; <span class="comment">// okay, definitely a boolean</span></span><br></pre></td></tr></table></figure>

<p>在对现有代码进行改写的时候，<code>any</code>类型是十分有用的，它允许你在编译时可选择地包含或移除类型检查。 你可能认为 <code>Object</code>有相似的作用，就像它在其它语言中那样。 但是 <code>Object</code>类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用任意的方法，即便它真的有这些方法：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> notSure: <span class="built_in">any</span> = <span class="number">4</span>;</span><br><span class="line">notSure.ifItExists(); <span class="comment">// okay, ifItExists might exist at runtime</span></span><br><span class="line">notSure.toFixed(); <span class="comment">// okay, toFixed exists (but the compiler doesn't check)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> prettySure: <span class="built_in">Object</span> = <span class="number">4</span>;</span><br><span class="line">prettySure.toFixed(); <span class="comment">// Error: Property 'toFixed' doesn't exist on type 'Object'.</span></span><br></pre></td></tr></table></figure>

<p>当你只知道一部分数据的类型时，<code>any</code>类型也是有用的。 比如，你有一个数组，它包含了不同的类型的数据：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> list: <span class="built_in">any</span>[] = [<span class="number">1</span>, <span class="literal">true</span>, <span class="string">"free"</span>];</span><br><span class="line">list[<span class="number">1</span>] = <span class="number">100</span>;</span><br></pre></td></tr></table></figure>

<h3 id="Void"><a href="#Void" class="headerlink" title="Void"></a>Void</h3><p>某种程度上来说，<code>void</code>类型像是与<code>any</code>类型相反，它表示没有任何类型。 当一个函数没有返回值时，你通常会见到其返回值类型是 <code>void</code>：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">warnUser</span>(<span class="params"></span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"This is my warning message"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>声明一个<code>void</code>类型的变量没有什么大用，因为你只能为它赋予<code>undefined</code>和<code>null</code>：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> unusable: <span class="built_in">void</span> = <span class="literal">undefined</span>;</span><br></pre></td></tr></table></figure>

<p>Null 和 Undefined：</p>
<p>TypeScript里，<code>undefined</code>和<code>null</code>两者各自有自己的类型分别叫做<code>undefined</code>和<code>null</code>。 和 <code>void</code>相似，它们的本身的类型用处不是很大：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Not much else we can assign to these variables!</span></span><br><span class="line"><span class="keyword">let</span> u: <span class="literal">undefined</span> = <span class="literal">undefined</span>;</span><br><span class="line"><span class="keyword">let</span> n: <span class="literal">null</span> = <span class="literal">null</span>;</span><br></pre></td></tr></table></figure>

<p>默认情况下<code>null</code>和<code>undefined</code>是所有类型的子类型。 就是说你可以把 <code>null</code>和<code>undefined</code>赋值给<code>number</code>类型的变量。</p>
<p>然而，当你指定了<code>--strictNullChecks</code>标记，<code>null</code>和<code>undefined</code>只能赋值给<code>void</code>和它们各自。 这能避免 <em>很多</em>常见的问题。 也许在某处你想传入一个 <code>string</code>或<code>null</code>或<code>undefined</code>，你可以使用联合类型<code>string | null | undefined</code>。 再次说明，稍后我们会介绍联合类型。</p>
<h3 id="Never"><a href="#Never" class="headerlink" title="Never"></a>Never</h3><p><code>never</code>类型表示的是那些永不存在的值的类型。 例如， <code>never</code>类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型； 变量也可能是 <code>never</code>类型，当它们被永不为真的类型保护所约束时。</p>
<p><code>never</code>类型是任何类型的子类型，也可以赋值给任何类型；然而，<em>没有</em>类型是<code>never</code>的子类型或可以赋值给<code>never</code>类型（除了<code>never</code>本身之外）。 即使 <code>any</code>也不可以赋值给<code>never</code>。</p>
<p>下面是一些返回<code>never</code>类型的函数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 返回never的函数必须存在无法达到的终点</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">error</span>(<span class="params">message: <span class="built_in">string</span></span>): <span class="title">never</span> </span>&#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(message);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 推断的返回值类型为never</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fail</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> error(<span class="string">"Something failed"</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 返回never的函数必须存在无法达到的终点</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">infiniteLoop</span>(<span class="params"></span>): <span class="title">never</span> </span>&#123;</span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="Object"><a href="#Object" class="headerlink" title="Object"></a>Object</h3><p><code>object</code>表示非原始类型，也就是除<code>number</code>，<code>string</code>，<code>boolean</code>，<code>symbol</code>，<code>null</code>或<code>undefined</code>之外的类型。</p>
<p>使用<code>object</code>类型，就可以更好的表示像<code>Object.create</code>这样的API。例如：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="function"><span class="keyword">function</span> <span class="title">create</span>(<span class="params">o: object | <span class="literal">null</span></span>): <span class="title">void</span></span>;</span><br><span class="line"></span><br><span class="line">create(&#123; prop: <span class="number">0</span> &#125;); <span class="comment">// OK</span></span><br><span class="line">create(<span class="literal">null</span>); <span class="comment">// OK</span></span><br><span class="line"></span><br><span class="line">create(<span class="number">42</span>); <span class="comment">// Error</span></span><br><span class="line">create(<span class="string">"string"</span>); <span class="comment">// Error</span></span><br><span class="line">create(<span class="literal">false</span>); <span class="comment">// Error</span></span><br><span class="line">create(<span class="literal">undefined</span>); <span class="comment">// Error</span></span><br></pre></td></tr></table></figure>

<h3 id="类型断言"><a href="#类型断言" class="headerlink" title="类型断言"></a>类型断言</h3><p>有时候你会遇到这样的情况，你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。</p>
<p>通过<em>类型断言</em>这种方式可以告诉编译器，“相信我，我知道自己在干什么”。 类型断言好比其它语言里的类型转换，但是不进行特殊的数据检查和解构。 它没有运行时的影响，只是在编译阶段起作用。 TypeScript会假设你，程序员，已经进行了必须的检查。</p>
<p>类型断言有两种形式。 其一是“尖括号”语法：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> someValue: <span class="built_in">any</span> = <span class="string">"this is a string"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> strLength: <span class="built_in">number</span> = (&lt;<span class="built_in">string</span>&gt;someValue).length;</span><br></pre></td></tr></table></figure>

<p>另一个为<code>as</code>语法：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> someValue: <span class="built_in">any</span> = <span class="string">"this is a string"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> strLength: <span class="built_in">number</span> = (someValue <span class="keyword">as</span> <span class="built_in">string</span>).length;</span><br></pre></td></tr></table></figure>

<p>两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好；然而，当你在TypeScript里使用JSX时，只有 <code>as</code>语法断言是被允许的。</p>
<h2 id="接口"><a href="#接口" class="headerlink" title="接口"></a>接口</h2><p>TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里，接口的作用就是<strong>为这些类型命名和为你的代码或第三方代码定义契约</strong>。</p>
<h3 id="接口初探"><a href="#接口初探" class="headerlink" title="接口初探"></a>接口初探</h3><p>下面通过一个简单示例来观察接口是如何工作的：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">printLabel</span>(<span class="params">labelledObj: &#123; label: <span class="built_in">string</span> &#125;</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(labelledObj.label);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myObj = &#123; size: <span class="number">10</span>, label: <span class="string">"Size 10 Object"</span> &#125;;</span><br><span class="line">printLabel(myObj);</span><br></pre></td></tr></table></figure>

<p>类型检查器会查看<code>printLabel</code>的调用。 <code>printLabel</code>有一个参数，并要求这个对象参数有一个名为<code>label</code>类型为<code>string</code>的属性。 需要注意的是，我们传入的对象参数实际上会包含很多属性，但是编译器只会检查那些必需的属性是否存在，并且其类型是否匹配。 然而，有些时候TypeScript却并不会这么宽松，我们下面会稍做讲解。</p>
<p>下面我们重写上面的例子，这次使用接口来描述：必须包含一个<code>label</code>属性且类型为<code>string</code>：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> LabelledValue &#123;</span><br><span class="line">  label: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">printLabel</span>(<span class="params">labelledObj: LabelledValue</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(labelledObj.label);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myObj = &#123;size: <span class="number">10</span>, label: <span class="string">"Size 10 Object"</span>&#125;;</span><br><span class="line">printLabel(myObj);</span><br></pre></td></tr></table></figure>

<p><code>LabelledValue</code>接口就好比一个名字，用来描述上面例子里的要求。 它代表了有一个 <code>label</code>属性且类型为<code>string</code>的对象。 需要注意的是，我们在这里并不能像在其它语言里一样，说传给 <code>printLabel</code>的对象实现了这个接口。我们只会去关注值的外形。 只要传入的对象满足上面提到的必要条件，那么它就是被允许的。</p>
<p>还有一点值得提的是，类型检查器不会去检查属性的顺序，只要相应的属性存在并且类型也是对的就可以。</p>
<h3 id="可选属性"><a href="#可选属性" class="headerlink" title="可选属性"></a>可选属性</h3><p>接口里的属性不全都是必需的。有些是只在某些条件下存在，或者根本不存在。可选属性在应用“option bags”模式时很常用，即给函数传入的参数对象中只有部分属性赋值了。</p>
<p>下面是应用了“option bags”的例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> SquareConfig &#123;</span><br><span class="line">  color?: <span class="built_in">string</span>;</span><br><span class="line">  width?: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createSquare</span>(<span class="params">config: SquareConfig</span>): </span>&#123;color: <span class="built_in">string</span>; area: <span class="built_in">number</span>&#125; &#123;</span><br><span class="line">  <span class="keyword">let</span> newSquare = &#123;color: <span class="string">"white"</span>, area: <span class="number">100</span>&#125;;</span><br><span class="line">  <span class="keyword">if</span> (config.color) &#123;</span><br><span class="line">    newSquare.color = config.color;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (config.width) &#123;</span><br><span class="line">    newSquare.area = config.width * config.width;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> newSquare;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> mySquare = createSquare(&#123;color: <span class="string">"black"</span>&#125;);</span><br></pre></td></tr></table></figure>

<p>带有可选属性的接口与普通的接口定义差不多，只是在可选属性名字定义的后面加一个<code>?</code>符号。</p>
<p>可选属性的好处之一是可以对可能存在的属性进行预定义，好处之二是可以捕获引用了不存在的属性时的错误。 比如，我们故意将 <code>createSquare</code>里的<code>color</code>属性名拼错，就会得到一个错误提示：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> SquareConfig &#123;</span><br><span class="line">  color?: <span class="built_in">string</span>;</span><br><span class="line">  width?: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createSquare</span>(<span class="params">config: SquareConfig</span>): </span>&#123; color: <span class="built_in">string</span>; area: <span class="built_in">number</span> &#125; &#123;</span><br><span class="line">  <span class="keyword">let</span> newSquare = &#123;color: <span class="string">"white"</span>, area: <span class="number">100</span>&#125;;</span><br><span class="line">  <span class="keyword">if</span> (config.clor) &#123;</span><br><span class="line">    <span class="comment">// Error: Property 'clor' does not exist on type 'SquareConfig'</span></span><br><span class="line">    newSquare.color = config.clor;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (config.width) &#123;</span><br><span class="line">    newSquare.area = config.width * config.width;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> newSquare;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> mySquare = createSquare(&#123;color: <span class="string">"black"</span>&#125;);</span><br></pre></td></tr></table></figure>

<h3 id="只读属性"><a href="#只读属性" class="headerlink" title="只读属性"></a>只读属性</h3><p>一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 <code>readonly</code>来指定只读属性:</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Point &#123;</span><br><span class="line">    readonly x: <span class="built_in">number</span>;</span><br><span class="line">    readonly y: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>你可以通过赋值一个对象字面量来构造一个<code>Point</code>。 赋值后， <code>x</code>和<code>y</code>再也不能被改变了。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> p1: Point = &#123; x: <span class="number">10</span>, y: <span class="number">20</span> &#125;;</span><br><span class="line">p1.x = <span class="number">5</span>; <span class="comment">// error!</span></span><br></pre></td></tr></table></figure>

<p>TypeScript具有<code>ReadonlyArray&lt;T&gt;</code>类型，它与<code>Array&lt;T&gt;</code>相似，只是把所有可变方法去掉了，因此可以确保数组创建后再也不能被修改：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a: <span class="built_in">number</span>[] = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>];</span><br><span class="line"><span class="keyword">let</span> ro: ReadonlyArray&lt;<span class="built_in">number</span>&gt; = a;</span><br><span class="line">ro[<span class="number">0</span>] = <span class="number">12</span>; <span class="comment">// error!</span></span><br><span class="line">ro.push(<span class="number">5</span>); <span class="comment">// error!</span></span><br><span class="line">ro.length = <span class="number">100</span>; <span class="comment">// error!</span></span><br><span class="line">a = ro; <span class="comment">// error!</span></span><br></pre></td></tr></table></figure>

<p>上面代码的最后一行，可以看到就算把整个<code>ReadonlyArray</code>赋值到一个普通数组也是不可以的。 但是你可以用类型断言重写：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a = ro <span class="keyword">as</span> <span class="built_in">number</span>[];</span><br></pre></td></tr></table></figure>

<p><code>readonly</code> vs <code>const</code></p>
<p>最简单判断该用<code>readonly</code>还是<code>const</code>的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 <code>const</code>，若做为属性则使用<code>readonly</code>。</p>
<h3 id="额外的属性检查"><a href="#额外的属性检查" class="headerlink" title="额外的属性检查"></a>额外的属性检查</h3><p>我们在第一个例子里使用了接口，TypeScript让我们传入<code>{ size: number; label: string; }</code>到仅期望得到<code>{ label: string; }</code>的函数里。 我们已经学过了可选属性，并且知道他们在“option bags”模式里很有用。</p>
<p>然而，天真地将这两者结合的话就会像在JavaScript里那样搬起石头砸自己的脚。 比如，拿 <code>createSquare</code>例子来说：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> SquareConfig &#123;</span><br><span class="line">    color?: <span class="built_in">string</span>;</span><br><span class="line">    width?: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createSquare</span>(<span class="params">config: SquareConfig</span>): </span>&#123; color: <span class="built_in">string</span>; area: <span class="built_in">number</span> &#125; &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> mySquare = createSquare(&#123; colour: <span class="string">"red"</span>, width: <span class="number">100</span> &#125;);</span><br></pre></td></tr></table></figure>

<p>注意传入<code>createSquare</code>的参数拼写为*<code>colour</code>*而不是<code>color</code>。 在JavaScript里，这会默默地失败。你可能会争辩这个程序已经正确地类型化了，因为<code>width</code>属性是兼容的，不存在<code>color</code>属性，而且额外的<code>colour</code>属性是无意义的。</p>
<p>然而，TypeScript会认为这段代码可能存在bug。 对象字面量会被特殊对待而且会经过 <em>额外属性检查</em>，当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时，你会得到一个错误。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// error: 'colour' not expected in type 'SquareConfig'</span></span><br><span class="line"><span class="keyword">let</span> mySquare = createSquare(&#123; colour: <span class="string">"red"</span>, width: <span class="number">100</span> &#125;);</span><br></pre></td></tr></table></figure>

<p>绕开这些检查非常简单。 最简便的方法是使用类型断言：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> mySquare = createSquare(&#123; width: <span class="number">100</span>, opacity: <span class="number">0.5</span> &#125; <span class="keyword">as</span> SquareConfig);</span><br></pre></td></tr></table></figure>

<p>然而，最佳的方式是能够添加一个字符串索引签名，前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性。 如果 <code>SquareConfig</code>带有上面定义的类型的<code>color</code>和<code>width</code>属性，并且<em>还会</em>带有任意数量的其它属性，那么我们可以这样定义它：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> SquareConfig &#123;</span><br><span class="line">    color?: <span class="built_in">string</span>;</span><br><span class="line">    width?: <span class="built_in">number</span>;</span><br><span class="line">    [propName: <span class="built_in">string</span>]: <span class="built_in">any</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们稍后会讲到索引签名，但在这我们要表示的是<code>SquareConfig</code>可以有任意数量的属性，并且只要它们不是<code>color</code>和<code>width</code>，那么就无所谓它们的类型是什么。</p>
<p>还有最后一种跳过这些检查的方式，这可能会让你感到惊讶，它就是将这个对象赋值给一个另一个变量： 因为 <code>squareOptions</code>不会经过额外属性检查，所以编译器不会报错。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> squareOptions = &#123; colour: <span class="string">"red"</span>, width: <span class="number">100</span> &#125;;</span><br><span class="line"><span class="keyword">let</span> mySquare = createSquare(squareOptions);</span><br></pre></td></tr></table></figure>

<p>要留意，在像上面一样的简单代码里，你可能不应该去绕开这些检查。 对于包含方法和内部状态的复杂对象字面量来讲，你可能需要使用这些技巧，但是大部额外属性检查错误是真正的bug。 就是说你遇到了额外类型检查出的错误，比如“option bags”，你应该去审查一下你的类型声明。 在这里，如果支持传入 <code>color</code>或<code>colour</code>属性到<code>createSquare</code>，你应该修改<code>SquareConfig</code>定义来体现出这一点。</p>
<h3 id="函数类型"><a href="#函数类型" class="headerlink" title="函数类型"></a>函数类型</h3><p>接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外，接口也可以描述函数类型。</p>
<p>为了使用接口表示函数类型，我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> SearchFunc &#123;</span><br><span class="line">  (source: <span class="built_in">string</span>, subString: <span class="built_in">string</span>): <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这样定义后，我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量，并将一个同类型的函数赋值给这个变量。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> mySearch: SearchFunc;</span><br><span class="line">mySearch = <span class="function"><span class="keyword">function</span>(<span class="params">source: <span class="built_in">string</span>, subString: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> result = source.search(subString);</span><br><span class="line">  <span class="keyword">return</span> result &gt; <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>对于函数类型的类型检查来说，函数的参数名不需要与接口里定义的名字相匹配。</strong> 比如，我们使用下面的代码重写上面的例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> mySearch: SearchFunc;</span><br><span class="line">mySearch = <span class="function"><span class="keyword">function</span>(<span class="params">src: <span class="built_in">string</span>, sub: <span class="built_in">string</span></span>): <span class="title">boolean</span> </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> result = src.search(sub);</span><br><span class="line">  <span class="keyword">return</span> result &gt; <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>函数的参数会逐个进行检查，要求对应位置上的参数类型是兼容的。</strong> 如果你不想指定类型，TypeScript的类型系统会推断出参数类型，因为函数直接赋值给了 <code>SearchFunc</code>类型变量。 函数的返回值类型是通过其返回值推断出来的（此例是 <code>false</code>和<code>true</code>）。 如果让这个函数返回数字或字符串，类型检查器会警告我们函数的返回值类型与 <code>SearchFunc</code>接口中的定义不匹配。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> mySearch: SearchFunc;</span><br><span class="line">mySearch = <span class="function"><span class="keyword">function</span>(<span class="params">src, sub</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> result = src.search(sub);</span><br><span class="line">    <span class="keyword">return</span> result &gt; <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="可索引的类型"><a href="#可索引的类型" class="headerlink" title="可索引的类型"></a>可索引的类型</h3><p>与使用接口描述函数类型差不多，我们也可以描述那些能够“通过索引得到”的类型，比如<code>a[10]</code>或<code>ageMap[&quot;daniel&quot;]</code>。 可索引类型具有一个 <em>索引签名</em>，它描述了对象索引的类型，还有相应的索引返回值类型。 让我们看一个例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> StringArray &#123;</span><br><span class="line">  [index: <span class="built_in">number</span>]: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myArray: StringArray;</span><br><span class="line">myArray = [<span class="string">"Bob"</span>, <span class="string">"Fred"</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myStr: <span class="built_in">string</span> = myArray[<span class="number">0</span>];</span><br></pre></td></tr></table></figure>

<p>上面例子里，我们定义了<code>StringArray</code>接口，它具有索引签名。 这个索引签名表示了当用 <code>number</code>去索引<code>StringArray</code>时会得到<code>string</code>类型的返回值。</p>
<p>TypeScript支持两种索引签名：字符串和数字。 可以同时使用两种类型的索引，但是<strong>数字索引的返回值必须是字符串索引返回值类型的子类型</strong>。 这是因为当使用 <code>number</code>来索引时，JavaScript会将它转换成<code>string</code>然后再去索引对象。 也就是说用 <code>100</code>（一个<code>number</code>）去索引等同于使用<code>&quot;100&quot;</code>（一个<code>string</code>）去索引，因此两者需要保持一致。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Animal &#123;</span><br><span class="line">    name: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> Dog <span class="keyword">extends</span> Animal &#123;</span><br><span class="line">    breed: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 错误：使用数值型的字符串索引，有时会得到完全不同的Animal!</span></span><br><span class="line"><span class="keyword">interface</span> NotOkay &#123;</span><br><span class="line">    [x: <span class="built_in">number</span>]: Animal;</span><br><span class="line">    [x: <span class="built_in">string</span>]: Dog;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>字符串索引签名能够很好的描述<code>dictionary</code>模式，并且它们也会确保所有属性与其返回值类型相匹配。 因为字符串索引声明了 <code>obj.property</code>和<code>obj[&quot;property&quot;]</code>两种形式都可以。 下面的例子里， <code>name</code>的类型与字符串索引类型不匹配，所以类型检查器给出一个错误提示：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> NumberDictionary &#123;</span><br><span class="line">  [index: <span class="built_in">string</span>]: <span class="built_in">number</span>;</span><br><span class="line">  length: <span class="built_in">number</span>;    <span class="comment">// 可以，length是number类型</span></span><br><span class="line">  name: <span class="built_in">string</span>       <span class="comment">// 错误，`name`的类型与索引类型返回值的类型不匹配</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>最后，你可以将索引签名设置为只读，这样就防止了给索引赋值：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> ReadonlyStringArray &#123;</span><br><span class="line">    readonly [index: <span class="built_in">number</span>]: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> myArray: ReadonlyStringArray = [<span class="string">"Alice"</span>, <span class="string">"Bob"</span>];</span><br><span class="line">myArray[<span class="number">2</span>] = <span class="string">"Mallory"</span>; <span class="comment">// error!</span></span><br></pre></td></tr></table></figure>

<p>你不能设置<code>myArray[2]</code>，因为索引签名是只读的。</p>
<h3 id="类类型"><a href="#类类型" class="headerlink" title="类类型"></a>类类型</h3><h4 id="实现接口"><a href="#实现接口" class="headerlink" title="实现接口"></a>实现接口</h4><p>与C#或Java里接口的基本作用一样，TypeScript也能够用它来明确的强制一个类去符合某种契约。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> ClockInterface &#123;</span><br><span class="line">    currentTime: <span class="built_in">Date</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Clock <span class="keyword">implements</span> ClockInterface &#123;</span><br><span class="line">    currentTime: <span class="built_in">Date</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">h: <span class="built_in">number</span>, m: <span class="built_in">number</span></span>) &#123; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>你也可以在接口中描述一个方法，在类里实现它，如同下面的<code>setTime</code>方法一样：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> ClockInterface &#123;</span><br><span class="line">    currentTime: <span class="built_in">Date</span>;</span><br><span class="line">    setTime(d: <span class="built_in">Date</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Clock <span class="keyword">implements</span> ClockInterface &#123;</span><br><span class="line">    currentTime: <span class="built_in">Date</span>;</span><br><span class="line">    setTime(d: <span class="built_in">Date</span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.currentTime = d;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">h: <span class="built_in">number</span>, m: <span class="built_in">number</span></span>) &#123; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接口描述了类的公共部分，而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。</p>
<h4 id="静态与实例部分"><a href="#静态与实例部分" class="headerlink" title="静态与实例部分"></a>静态与实例部分</h4><p>当你操作类和接口的时候，你要知道类是具有两个类型的：静态部分的类型和实例的类型。 你会注意到，当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> ClockConstructor &#123;</span><br><span class="line">    <span class="keyword">new</span> (hour: <span class="built_in">number</span>, minute: <span class="built_in">number</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Clock <span class="keyword">implements</span> ClockConstructor &#123;</span><br><span class="line">    currentTime: <span class="built_in">Date</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">h: <span class="built_in">number</span>, m: <span class="built_in">number</span></span>) &#123; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里因为<strong>当一个类实现了一个接口时，只对其实例部分进行类型检查。 constructor存在于类的静态部分，所以不在检查的范围内。</strong></p>
<p>因此，我们应该直接操作类的静态部分。 看下面的例子，我们定义了两个接口， <code>ClockConstructor</code>为构造函数所用和<code>ClockInterface</code>为实例方法所用。 为了方便我们定义一个构造函数 <code>createClock</code>，它用传入的类型创建实例。 </p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> ClockConstructor &#123;</span><br><span class="line">    <span class="keyword">new</span> (hour: <span class="built_in">number</span>, minute: <span class="built_in">number</span>): ClockInterface;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> ClockInterface &#123;</span><br><span class="line">    tick();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createClock</span>(<span class="params">ctor: ClockConstructor, hour: <span class="built_in">number</span>, minute: <span class="built_in">number</span></span>): <span class="title">ClockInterface</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> ctor(hour, minute);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> DigitalClock <span class="keyword">implements</span> ClockInterface &#123;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">h: <span class="built_in">number</span>, m: <span class="built_in">number</span></span>) &#123; &#125;</span><br><span class="line">    tick() &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"beep beep"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> AnalogClock <span class="keyword">implements</span> ClockInterface &#123;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">h: <span class="built_in">number</span>, m: <span class="built_in">number</span></span>) &#123; &#125;</span><br><span class="line">    tick() &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"tick tock"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> digital = createClock(DigitalClock, <span class="number">12</span>, <span class="number">17</span>);</span><br><span class="line"><span class="keyword">let</span> analog = createClock(AnalogClock, <span class="number">7</span>, <span class="number">32</span>);</span><br></pre></td></tr></table></figure>

<p>因为<code>createClock</code>的第一个参数是<code>ClockConstructor</code>类型，在<code>createClock(AnalogClock, 7, 32)</code>里，会检查<code>AnalogClock</code>是否符合构造函数签名。</p>
<h3 id="继承接口"><a href="#继承接口" class="headerlink" title="继承接口"></a>继承接口</h3><p>和类一样，接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里，可以更灵活地将接口分割到可重用的模块里。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Shape &#123;</span><br><span class="line">    color: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Square <span class="keyword">extends</span> Shape &#123;</span><br><span class="line">    sideLength: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> square = &lt;Square&gt;&#123;&#125;;</span><br><span class="line">square.color = <span class="string">"blue"</span>;</span><br><span class="line">square.sideLength = <span class="number">10</span>;</span><br></pre></td></tr></table></figure>

<p>一个接口可以继承多个接口，创建出多个接口的合成接口。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Shape &#123;</span><br><span class="line">    color: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> PenStroke &#123;</span><br><span class="line">    penWidth: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Square <span class="keyword">extends</span> Shape, PenStroke &#123;</span><br><span class="line">    sideLength: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> square = &lt;Square&gt;&#123;&#125;;</span><br><span class="line">square.color = <span class="string">"blue"</span>;</span><br><span class="line">square.sideLength = <span class="number">10</span>;</span><br><span class="line">square.penWidth = <span class="number">5.0</span>;</span><br></pre></td></tr></table></figure>

<h3 id="混合类型"><a href="#混合类型" class="headerlink" title="混合类型"></a>混合类型</h3><p>先前我们提过，接口能够描述JavaScript里丰富的类型。 因为JavaScript其动态灵活的特点，有时你会希望一个对象可以同时具有上面提到的多种类型。一个例子就是，一个对象可以同时做为函数和对象使用，并带有额外的属性。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Counter &#123;</span><br><span class="line">    (start: <span class="built_in">number</span>): <span class="built_in">string</span>;</span><br><span class="line">    interval: <span class="built_in">number</span>;</span><br><span class="line">    reset(): <span class="built_in">void</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getCounter</span>(<span class="params"></span>): <span class="title">Counter</span> </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> counter = &lt;Counter&gt;<span class="function"><span class="keyword">function</span> (<span class="params">start: <span class="built_in">number</span></span>) </span>&#123; &#125;;</span><br><span class="line">    counter.interval = <span class="number">123</span>;</span><br><span class="line">    counter.reset = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123; &#125;;</span><br><span class="line">    <span class="keyword">return</span> counter;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> c = getCounter();</span><br><span class="line">c(<span class="number">10</span>);</span><br><span class="line">c.reset();</span><br><span class="line">c.interval = <span class="number">5.0</span>;</span><br></pre></td></tr></table></figure>

<p>在使用JavaScript第三方库的时候，你可能需要像上面那样去完整地定义类型。</p>
<h3 id="接口继承类"><a href="#接口继承类" class="headerlink" title="接口继承类"></a>接口继承类</h3><p>当接口继承了一个类类型时，它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员，但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 <strong>这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时，这个接口类型只能被这个类或其子类所实现（implement）</strong>。</p>
<p>当你有一个庞大的继承结构时这很有用，但要指出的是你的代码只在子类拥有特定属性时起作用。 这个子类除了继承至基类外与基类没有任何关系。 例：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Control &#123;</span><br><span class="line">    <span class="keyword">private</span> state: <span class="built_in">any</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> SelectableControl <span class="keyword">extends</span> Control &#123;</span><br><span class="line">    select(): <span class="built_in">void</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Button <span class="keyword">extends</span> Control <span class="keyword">implements</span> SelectableControl &#123;</span><br><span class="line">    select() &#123; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> TextBox <span class="keyword">extends</span> Control &#123;</span><br><span class="line">    select() &#123; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 错误：“Image”类型缺少“state”属性。</span></span><br><span class="line"><span class="keyword">class</span> Image <span class="keyword">implements</span> SelectableControl &#123;</span><br><span class="line">    select() &#123; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Location &#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面的例子里，<code>SelectableControl</code>包含了<code>Control</code>的所有成员，包括私有成员<code>state</code>。 因为 <code>state</code>是私有成员，所以只能够是<code>Control</code>的子类们才能实现<code>SelectableControl</code>接口。 因为只有 <code>Control</code>的子类才能够拥有一个声明于<code>Control</code>的私有成员<code>state</code>，这对私有成员的兼容性是必需的。</p>
<p>在<code>Control</code>类内部，是允许通过<code>SelectableControl</code>的实例来访问私有成员<code>state</code>的。 实际上， <code>SelectableControl</code>接口和拥有<code>select</code>方法的<code>Control</code>类是一样的。 <code>Button</code>和<code>TextBox</code>类是<code>SelectableControl</code>的子类（因为它们都继承自<code>Control</code>并有<code>select</code>方法），但<code>Image</code>和<code>Location</code>类并不是这样的。</p>
<h2 id="类"><a href="#类" class="headerlink" title="类"></a>类</h2><p>传统的JavaScript程序使用函数和基于原型的继承来创建可重用的组件，但对于熟悉使用面向对象方式的程序员来讲就有些棘手，因为他们用的是基于类的继承并且对象是由类构建出来的。 从ECMAScript 2015，也就是ECMAScript 6开始，JavaScript程序员将能够使用基于类的面向对象的方式。 使用TypeScript，我们允许开发者现在就使用这些特性，并且编译后的JavaScript可以在所有主流浏览器和平台上运行，而不需要等到下个JavaScript版本。</p>
<p>下面看一个使用类的例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    greeting: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">message: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.greeting = message;</span><br><span class="line">    &#125;</span><br><span class="line">    greet() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Hello, "</span> + <span class="keyword">this</span>.greeting;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> greeter = <span class="keyword">new</span> Greeter(<span class="string">"world"</span>);</span><br></pre></td></tr></table></figure>

<p>如果你使用过C#或Java，你会对这种语法非常熟悉。 我们声明一个 <code>Greeter</code>类。这个类有3个成员：一个叫做 <code>greeting</code>的属性，一个构造函数和一个 <code>greet</code>方法。</p>
<p>你会注意到，我们在引用任何一个类成员的时候都用了 <code>this</code>。 它表示我们访问的是类的成员。</p>
<p>最后一行，我们使用 <code>new</code>构造了 <code>Greeter</code>类的一个实例。 它会调用之前定义的构造函数，创建一个 <code>Greeter</code>类型的新对象，并执行构造函数初始化它。</p>
<h3 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h3><p>在TypeScript里，我们可以使用常用的面向对象模式。 基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。</p>
<p>看下面的例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Animal &#123;</span><br><span class="line">    move(distanceInMeters: <span class="built_in">number</span> = <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">`Animal moved <span class="subst">$&#123;distanceInMeters&#125;</span>m.`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Dog <span class="keyword">extends</span> Animal &#123;</span><br><span class="line">    bark() &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'Woof! Woof!'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dog = <span class="keyword">new</span> Dog();</span><br><span class="line">dog.bark();</span><br><span class="line">dog.move(<span class="number">10</span>);</span><br><span class="line">dog.bark();</span><br></pre></td></tr></table></figure>

<p>这个例子展示了最基本的继承：类从基类中继承了属性和方法。 这里， <code>Dog</code>是一个 <em>派生类</em>，它派生自 <code>Animal</code> <em>基类</em>，通过 <code>extends</code>关键字。 派生类通常被称作 <em>子类</em>，基类通常被称作 <em>超类</em>。</p>
<p>因为 <code>Dog</code>继承了 <code>Animal</code>的功能，因此我们可以创建一个 <code>Dog</code>的实例，它能够 <code>bark()</code>和 <code>move()</code>。</p>
<p>下面我们来看个更加复杂的例子。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Animal &#123;</span><br><span class="line">    name: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">theName: <span class="built_in">string</span></span>) &#123; <span class="keyword">this</span>.name = theName; &#125;</span><br><span class="line">    move(distanceInMeters: <span class="built_in">number</span> = <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">`<span class="subst">$&#123;<span class="keyword">this</span>.name&#125;</span> moved <span class="subst">$&#123;distanceInMeters&#125;</span>m.`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Snake <span class="keyword">extends</span> Animal &#123;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">name: <span class="built_in">string</span></span>) &#123; <span class="keyword">super</span>(name); &#125;</span><br><span class="line">    move(distanceInMeters = <span class="number">5</span>) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"Slithering..."</span>);</span><br><span class="line">        <span class="keyword">super</span>.move(distanceInMeters);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Horse <span class="keyword">extends</span> Animal &#123;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">name: <span class="built_in">string</span></span>) &#123; <span class="keyword">super</span>(name); &#125;</span><br><span class="line">    move(distanceInMeters = <span class="number">45</span>) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"Galloping..."</span>);</span><br><span class="line">        <span class="keyword">super</span>.move(distanceInMeters);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> sam = <span class="keyword">new</span> Snake(<span class="string">"Sammy the Python"</span>);</span><br><span class="line"><span class="keyword">let</span> tom: Animal = <span class="keyword">new</span> Horse(<span class="string">"Tommy the Palomino"</span>);</span><br><span class="line"></span><br><span class="line">sam.move();</span><br><span class="line">tom.move(<span class="number">34</span>);</span><br></pre></td></tr></table></figure>

<p>这个例子展示了一些上面没有提到的特性。 这一次，我们使用 <code>extends</code>关键字创建了 <code>Animal</code>的两个子类： <code>Horse</code>和 <code>Snake</code>。</p>
<p>与前一个例子的不同点是，派生类包含了一个构造函数，它 <em>必须</em>调用 <code>super()</code>，它会执行基类的构造函数。 而且，在构造函数里访问 <code>this</code>的属性之前，我们 <em>一定</em>要调用 <code>super()</code>。 这个是TypeScript强制执行的一条重要规则。</p>
<p>这个例子演示了如何在子类里可以重写父类的方法。 <code>Snake</code>类和 <code>Horse</code>类都创建了 <code>move</code>方法，它们重写了从 <code>Animal</code>继承来的 <code>move</code>方法，使得 <code>move</code>方法根据不同的类而具有不同的功能。 注意，即使 <code>tom</code>被声明为 <code>Animal</code>类型，但因为它的值是 <code>Horse</code>，调用 <code>tom.move(34)</code>时，它会调用 <code>Horse</code>里重写的方法：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Slithering...</span><br><span class="line">Sammy the Python moved 5m.</span><br><span class="line">Galloping...</span><br><span class="line">Tommy the Palomino moved 34m.</span><br></pre></td></tr></table></figure>

<h3 id="公共，私有与受保护的修饰符"><a href="#公共，私有与受保护的修饰符" class="headerlink" title="公共，私有与受保护的修饰符"></a>公共，私有与受保护的修饰符</h3><p>默认为 <code>public</code>：</p>
<p>在上面的例子里，我们可以自由的访问程序里定义的成员。 如果你对其它语言中的类比较了解，就会注意到我们在之前的代码里并没有使用 <code>public</code>来做修饰；例如，C#要求必须明确地使用 <code>public</code>指定成员是可见的。 在TypeScript里，成员都默认为 <code>public</code>。</p>
<p>你也可以明确的将一个成员标记成 <code>public</code>。 我们可以用下面的方式来重写上面的 <code>Animal</code>类：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Animal &#123;</span><br><span class="line">    <span class="keyword">public</span> name: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">constructor</span>(<span class="params">theName: <span class="built_in">string</span></span>) &#123; <span class="keyword">this</span>.name = theName; &#125;</span><br><span class="line">    <span class="keyword">public</span> move(distanceInMeters: <span class="built_in">number</span>) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">`<span class="subst">$&#123;<span class="keyword">this</span>.name&#125;</span> moved <span class="subst">$&#123;distanceInMeters&#125;</span>m.`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>理解 <code>private</code>：</p>
<p>当成员被标记成 <code>private</code>时，它就不能在声明它的类的外部访问。比如：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Animal &#123;</span><br><span class="line">    <span class="keyword">private</span> name: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">theName: <span class="built_in">string</span></span>) &#123; <span class="keyword">this</span>.name = theName; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> Animal(<span class="string">"Cat"</span>).name; <span class="comment">// 错误: 'name' 是私有的.</span></span><br><span class="line"><span class="comment">//可以通过暴露公有方法的形式，来修改、访问私有变量属性</span></span><br></pre></td></tr></table></figure>

<p>TypeScript使用的是结构性类型系统。 当我们比较两种不同的类型时，并不在乎它们从何处而来，如果所有成员的类型都是兼容的，我们就认为它们的类型是兼容的。</p>
<p>然而，当我们比较带有 <code>private</code>或 <code>protected</code>成员的类型的时候，情况就不同了。 如果其中一个类型里包含一个 <code>private</code>成员，那么只有当另外一个类型中也存在这样一个 <code>private</code>成员， 并且它们都是来自同一处声明时，我们才认为这两个类型是兼容的。 对于 <code>protected</code>成员也使用这个规则。</p>
<p>下面来看一个例子，更好地说明了这一点：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Animal &#123;</span><br><span class="line">    <span class="keyword">private</span> name: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">theName: <span class="built_in">string</span></span>) &#123; <span class="keyword">this</span>.name = theName; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Rhino <span class="keyword">extends</span> Animal &#123;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params"></span>) &#123; <span class="keyword">super</span>(<span class="string">"Rhino"</span>); &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Employee &#123;</span><br><span class="line">    <span class="keyword">private</span> name: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">theName: <span class="built_in">string</span></span>) &#123; <span class="keyword">this</span>.name = theName; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> animal = <span class="keyword">new</span> Animal(<span class="string">"Goat"</span>);</span><br><span class="line"><span class="keyword">let</span> rhino = <span class="keyword">new</span> Rhino();</span><br><span class="line"><span class="keyword">let</span> employee = <span class="keyword">new</span> Employee(<span class="string">"Bob"</span>);</span><br><span class="line"></span><br><span class="line">animal = rhino;</span><br><span class="line">animal = employee; <span class="comment">// 错误: Animal 与 Employee 不兼容.</span></span><br></pre></td></tr></table></figure>

<p>这个例子中有 <code>Animal</code>和 <code>Rhino</code>两个类， <code>Rhino</code>是 <code>Animal</code>类的子类。 还有一个 <code>Employee</code>类，其类型看上去与 <code>Animal</code>是相同的。 我们创建了几个这些类的实例，并相互赋值来看看会发生什么。 因为 <code>Animal</code>和 <code>Rhino</code>共享了来自 <code>Animal</code>里的私有成员定义 <code>private name: string</code>，因此它们是兼容的。 然而 <code>Employee</code>却不是这样。当把 <code>Employee</code>赋值给 <code>Animal</code>的时候，得到一个错误，说它们的类型不兼容。 尽管 <code>Employee</code>里也有一个私有成员 <code>name</code>，但它明显不是 <code>Animal</code>里面定义的那个。</p>
<p>理解 <code>protected</code>：</p>
<p><code>protected</code>修饰符与 <code>private</code>修饰符的行为很相似，但有一点不同， <code>protected</code>成员在派生类中仍然可以访问。例如：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Person &#123;</span><br><span class="line">    <span class="keyword">protected</span> name: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">name: <span class="built_in">string</span></span>) &#123; <span class="keyword">this</span>.name = name; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Employee <span class="keyword">extends</span> Person &#123;</span><br><span class="line">    <span class="keyword">private</span> department: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">name: <span class="built_in">string</span>, department: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">super</span>(name)</span><br><span class="line">        <span class="keyword">this</span>.department = department;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> getElevatorPitch() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">`Hello, my name is <span class="subst">$&#123;<span class="keyword">this</span>.name&#125;</span> and I work in <span class="subst">$&#123;<span class="keyword">this</span>.department&#125;</span>.`</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> howard = <span class="keyword">new</span> Employee(<span class="string">"Howard"</span>, <span class="string">"Sales"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(howard.getElevatorPitch());</span><br><span class="line"><span class="built_in">console</span>.log(howard.name); <span class="comment">// 错误</span></span><br></pre></td></tr></table></figure>

<p>注意，我们不能在 <code>Person</code>类外使用 <code>name</code>，但是我们仍然可以通过 <code>Employee</code>类的实例方法访问，因为 <code>Employee</code>是由 <code>Person</code>派生而来的。</p>
<p>构造函数也可以被标记成 <code>protected</code>。 这意味着这个类不能在包含它的类外被实例化，但是能被继承。比如，</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Person &#123;</span><br><span class="line">    <span class="keyword">protected</span> name: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">constructor</span>(<span class="params">theName: <span class="built_in">string</span></span>) &#123; <span class="keyword">this</span>.name = theName; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Employee 能够继承 Person</span></span><br><span class="line"><span class="keyword">class</span> Employee <span class="keyword">extends</span> Person &#123;</span><br><span class="line">    <span class="keyword">private</span> department: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">name: <span class="built_in">string</span>, department: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">super</span>(name);</span><br><span class="line">        <span class="keyword">this</span>.department = department;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> getElevatorPitch() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">`Hello, my name is <span class="subst">$&#123;<span class="keyword">this</span>.name&#125;</span> and I work in <span class="subst">$&#123;<span class="keyword">this</span>.department&#125;</span>.`</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> howard = <span class="keyword">new</span> Employee(<span class="string">"Howard"</span>, <span class="string">"Sales"</span>);</span><br><span class="line"><span class="keyword">let</span> john = <span class="keyword">new</span> Person(<span class="string">"John"</span>); <span class="comment">// 错误: 'Person' 的构造函数是被保护的.</span></span><br></pre></td></tr></table></figure>

<h3 id="readonly修饰符"><a href="#readonly修饰符" class="headerlink" title="readonly修饰符"></a>readonly修饰符</h3><p>你可以使用 <code>readonly</code>关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Octopus &#123;</span><br><span class="line">    readonly name: <span class="built_in">string</span>;</span><br><span class="line">    readonly numberOfLegs: <span class="built_in">number</span> = <span class="number">8</span>;</span><br><span class="line">    <span class="keyword">constructor</span> (<span class="params">theName: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.name = theName;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> dad = <span class="keyword">new</span> Octopus(<span class="string">"Man with the 8 strong legs"</span>);</span><br><span class="line">dad.name = <span class="string">"Man with the 3-piece suit"</span>; <span class="comment">// 错误! name 是只读的.</span></span><br></pre></td></tr></table></figure>

<h3 id="参数属性"><a href="#参数属性" class="headerlink" title="参数属性"></a>参数属性</h3><p>在上面的例子中，我们必须在<code>Octopus</code>类里定义一个只读成员 <code>name</code>和一个参数为 <code>theName</code>的构造函数，并且立刻将 <code>theName</code>的值赋给 <code>name</code>，这种情况经常会遇到。 <em>参数属性</em>可以方便地让我们在一个地方定义并初始化一个成员。 下面的例子是对之前 <code>Octopus</code>类的修改版，使用了参数属性：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Octopus &#123;</span><br><span class="line">    readonly numberOfLegs: <span class="built_in">number</span> = <span class="number">8</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">readonly name: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>注意看我们是如何舍弃了 <code>theName</code>，仅在构造函数里使用 <code>readonly name: string</code>参数来创建和初始化 <code>name</code>成员。 我们把声明和赋值合并至一处。</p>
<p>参数属性通过给构造函数参数前面添加一个访问限定符来声明。 使用 <code>private</code>限定一个参数属性会声明并初始化一个私有成员；对于 <code>public</code>和 <code>protected</code>来说也是一样。</p>
<h3 id="存取器"><a href="#存取器" class="headerlink" title="存取器"></a>存取器</h3><p>TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。</p>
<p>下面来看如何把一个简单的类改写成使用 <code>get</code>和 <code>set</code>。 首先，我们从一个没有使用存取器的例子开始。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Employee &#123;</span><br><span class="line">    fullName: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> employee = <span class="keyword">new</span> Employee();</span><br><span class="line">employee.fullName = <span class="string">"Bob Smith"</span>;</span><br><span class="line"><span class="keyword">if</span> (employee.fullName) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(employee.fullName);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以随意的设置 <code>fullName</code>，这是非常方便的，但是这也可能会带来麻烦。</p>
<p>下面这个版本里，我们先检查用户密码是否正确，然后再允许其修改员工信息。 我们把对 <code>fullName</code>的直接访问改成了可以检查密码的 <code>set</code>方法。 我们也加了一个 <code>get</code>方法，让上面的例子仍然可以工作。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> passcode = <span class="string">"secret passcode"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Employee &#123;</span><br><span class="line">    <span class="keyword">private</span> _fullName: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">get</span> fullName(): <span class="built_in">string</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>._fullName;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">set</span> fullName(newName: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (passcode &amp;&amp; passcode == <span class="string">"secret passcode"</span>) &#123;</span><br><span class="line">            <span class="keyword">this</span>._fullName = newName;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="built_in">console</span>.log(<span class="string">"Error: Unauthorized update of employee!"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> employee = <span class="keyword">new</span> Employee();</span><br><span class="line">employee.fullName = <span class="string">"Bob Smith"</span>;</span><br><span class="line"><span class="keyword">if</span> (employee.fullName) &#123;</span><br><span class="line">    alert(employee.fullName);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以修改一下密码，来验证一下存取器是否是工作的。当密码不对时，会提示我们没有权限去修改员工。</p>
<p>对于存取器有下面几点需要注意的：</p>
<p>首先，存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次，只带有 <code>get</code>不带有 <code>set</code>的存取器自动被推断为 <code>readonly</code>。 这在从代码生成 <code>.d.ts</code>文件时是有帮助的，因为利用这个属性的用户会看到不允许够改变它的值。</p>
<h3 id="静态属性"><a href="#静态属性" class="headerlink" title="静态属性"></a>静态属性</h3><p>到目前为止，我们只讨论了类的实例成员，那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员，这些属性存在于类本身上面而不是类的实例上。 在这个例子里，我们使用 <code>static</code>定义 <code>origin</code>，因为它是所有网格都会用到的属性。 每个实例想要访问这个属性的时候，都要在 <code>origin</code>前面加上类名。 如同在实例属性上使用 <code>this.</code>前缀来访问属性一样，这里我们使用 <code>Grid.</code>来访问静态属性。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Grid &#123;</span><br><span class="line">    <span class="keyword">static</span> origin = &#123;x: <span class="number">0</span>, y: <span class="number">0</span>&#125;;</span><br><span class="line">    calculateDistanceFromOrigin(point: &#123;x: <span class="built_in">number</span>; y: <span class="built_in">number</span>;&#125;) &#123;</span><br><span class="line">        <span class="keyword">let</span> xDist = (point.x - Grid.origin.x);</span><br><span class="line">        <span class="keyword">let</span> yDist = (point.y - Grid.origin.y);</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">Math</span>.sqrt(xDist * xDist + yDist * yDist) / <span class="keyword">this</span>.scale;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">constructor</span> (<span class="params"><span class="keyword">public</span> scale: <span class="built_in">number</span></span>) &#123; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> grid1 = <span class="keyword">new</span> Grid(<span class="number">1.0</span>);  <span class="comment">// 1x scale</span></span><br><span class="line"><span class="keyword">let</span> grid2 = <span class="keyword">new</span> Grid(<span class="number">5.0</span>);  <span class="comment">// 5x scale</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(grid1.calculateDistanceFromOrigin(&#123;x: <span class="number">10</span>, y: <span class="number">10</span>&#125;));</span><br><span class="line"><span class="built_in">console</span>.log(grid2.calculateDistanceFromOrigin(&#123;x: <span class="number">10</span>, y: <span class="number">10</span>&#125;));</span><br></pre></td></tr></table></figure>

<h3 id="抽象类"><a href="#抽象类" class="headerlink" title="抽象类"></a>抽象类</h3><p>抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口，<strong>抽象类可以包含成员的实现细节</strong>。 <code>abstract</code>关键字是用于定义抽象类和在抽象类内部定义抽象方法。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> Animal &#123;</span><br><span class="line">    <span class="keyword">abstract</span> makeSound(): <span class="built_in">void</span>;</span><br><span class="line">    move(): <span class="built_in">void</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'roaming the earch...'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而，抽象方法必须包含 <code>abstract</code>关键字并且可以包含访问修饰符。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> Department &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params"><span class="keyword">public</span> name: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    printName(): <span class="built_in">void</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'Department name: '</span> + <span class="keyword">this</span>.name);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">abstract</span> printMeeting(): <span class="built_in">void</span>; <span class="comment">// 必须在派生类中实现</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> AccountingDepartment <span class="keyword">extends</span> Department &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">super</span>(<span class="string">'Accounting and Auditing'</span>); <span class="comment">// 在派生类的构造函数中必须调用 super()</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    printMeeting(): <span class="built_in">void</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'The Accounting Department meets each Monday at 10am.'</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    generateReports(): <span class="built_in">void</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'Generating accounting reports...'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> department: Department; <span class="comment">// 允许创建一个对抽象类型的引用</span></span><br><span class="line">department = <span class="keyword">new</span> Department(); <span class="comment">// 错误: 不能创建一个抽象类的实例</span></span><br><span class="line">department = <span class="keyword">new</span> AccountingDepartment(); <span class="comment">// 允许对一个抽象子类进行实例化和赋值</span></span><br><span class="line">department.printName();</span><br><span class="line">department.printMeeting();</span><br><span class="line">department.generateReports(); <span class="comment">// 错误: 方法在声明的抽象类中不存在</span></span><br></pre></td></tr></table></figure>

<h3 id="高级技巧"><a href="#高级技巧" class="headerlink" title="高级技巧"></a>高级技巧</h3><h4 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h4><p>当你在TypeScript里声明了一个类的时候，实际上同时声明了很多东西。 首先就是类的 <em>实例</em>的类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    greeting: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">message: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.greeting = message;</span><br><span class="line">    &#125;</span><br><span class="line">    greet() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Hello, "</span> + <span class="keyword">this</span>.greeting;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> greeter: Greeter;</span><br><span class="line">greeter = <span class="keyword">new</span> Greeter(<span class="string">"world"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(greeter.greet());</span><br></pre></td></tr></table></figure>

<p>这里，我们写了 <code>let greeter: Greeter</code>，意思是 <code>Greeter</code>类的实例的类型是 <code>Greeter</code>。 这对于用过其它面向对象语言的程序员来讲已经是老习惯了。</p>
<p>我们也创建了一个叫做 <em>构造函数</em>的值。 这个函数会在我们使用 <code>new</code>创建类实例的时候被调用。 下面我们来看看，上面的代码被编译成JavaScript后是什么样子的：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> Greeter = (<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">Greeter</span>(<span class="params">message</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.greeting = message;</span><br><span class="line">    &#125;</span><br><span class="line">    Greeter.prototype.greet = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Hello, "</span> + <span class="keyword">this</span>.greeting;</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">return</span> Greeter;</span><br><span class="line">&#125;)();</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> greeter;</span><br><span class="line">greeter = <span class="keyword">new</span> Greeter(<span class="string">"world"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(greeter.greet());</span><br></pre></td></tr></table></figure>

<p>上面的代码里， <code>let Greeter</code>将被赋值为构造函数。 当我们调用 <code>new</code>并执行了这个函数后，便会得到一个类的实例。 这个构造函数也包含了类的所有静态属性。 换个角度说，我们可以认为类具有 <em>实例部分</em>与 <em>静态部分</em>这两个部分。</p>
<p>让我们稍微改写一下这个例子，看看它们之间的区别：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    <span class="keyword">static</span> standardGreeting = <span class="string">"Hello, there"</span>;</span><br><span class="line">    greeting: <span class="built_in">string</span>;</span><br><span class="line">    greet() &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">this</span>.greeting) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"Hello, "</span> + <span class="keyword">this</span>.greeting;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> Greeter.standardGreeting;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> greeter1: Greeter;</span><br><span class="line">greeter1 = <span class="keyword">new</span> Greeter();</span><br><span class="line"><span class="built_in">console</span>.log(greeter1.greet());</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> greeterMaker: <span class="keyword">typeof</span> Greeter = Greeter;</span><br><span class="line">greeterMaker.standardGreeting = <span class="string">"Hey there!"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> greeter2: Greeter = <span class="keyword">new</span> greeterMaker();</span><br><span class="line"><span class="built_in">console</span>.log(greeter2.greet());</span><br></pre></td></tr></table></figure>

<p>这个例子里， <code>greeter1</code>与之前看到的一样。 我们实例化 <code>Greeter</code>类，并使用这个对象。 与我们之前看到的一样。</p>
<p>再之后，我们直接使用类。 我们创建了一个叫做 <code>greeterMaker</code>的变量。 这个变量保存了这个类或者说保存了类构造函数。 然后我们使用 <code>typeof Greeter</code>，意思是取Greeter类的类型，而不是实例的类型。 或者更确切的说，”告诉我 <code>Greeter</code>标识符的类型”，也就是构造函数的类型。 这个类型包含了类的所有静态成员和构造函数。 之后，就和前面一样，我们在 <code>greeterMaker</code>上使用 <code>new</code>，创建 <code>Greeter</code>的实例。</p>
<h4 id="把类当做接口使用"><a href="#把类当做接口使用" class="headerlink" title="把类当做接口使用"></a>把类当做接口使用</h4><p>如上一节里所讲的，类定义会创建两个东西：类的实例类型和一个构造函数。 因为类可以创建出类型，所以你能够在允许使用接口的地方使用类。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Point &#123;</span><br><span class="line">    x: <span class="built_in">number</span>;</span><br><span class="line">    y: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Point3d <span class="keyword">extends</span> Point &#123;</span><br><span class="line">    z: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> point3d: Point3d = &#123;x: <span class="number">1</span>, y: <span class="number">2</span>, z: <span class="number">3</span>&#125;;</span><br></pre></td></tr></table></figure>

<h2 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h2><p>函数是JavaScript应用程序的基础。 它帮助你实现抽象层，模拟类，信息隐藏和模块。 在TypeScript里，虽然已经支持类，命名空间和模块，但函数仍然是主要的定义 <em>行为</em>的地方。 TypeScript为JavaScript函数添加了额外的功能，让我们可以更容易地使用。</p>
<p>和JavaScript一样，TypeScript函数可以创建有名字的函数和匿名函数。 你可以随意选择适合应用程序的方式，不论是定义一系列API函数还是只使用一次的函数。</p>
<p>通过下面的例子可以迅速回想起这两种JavaScript中的函数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Named function</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">add</span>(<span class="params">x, y</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> x + y;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Anonymous function</span></span><br><span class="line"><span class="keyword">let</span> myAdd = <span class="function"><span class="keyword">function</span>(<span class="params">x, y</span>) </span>&#123; <span class="keyword">return</span> x + y; &#125;;</span><br></pre></td></tr></table></figure>

<p>在JavaScript里，函数可以使用函数体外部的变量。 当函数这么做时，我们说它‘捕获’了这些变量。 至于为什么可以这样做以及其中的利弊超出了本文的范围，但是深刻理解这个机制对学习JavaScript和TypeScript会很有帮助。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> z = <span class="number">100</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">addToZ</span>(<span class="params">x, y</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> x + y + z;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="函数类型-1"><a href="#函数类型-1" class="headerlink" title="函数类型"></a>函数类型</h3><p>让我们为上面那个函数添加类型：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">add</span>(<span class="params">x: <span class="built_in">number</span>, y: <span class="built_in">number</span></span>): <span class="title">number</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> x + y;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myAdd = <span class="function"><span class="keyword">function</span>(<span class="params">x: <span class="built_in">number</span>, y: <span class="built_in">number</span></span>): <span class="title">number</span> </span>&#123; <span class="keyword">return</span> x + y; &#125;;</span><br></pre></td></tr></table></figure>

<p>我们可以给每个参数添加类型之后再为函数本身添加返回值类型。 TypeScript能够根据返回语句自动推断出返回值类型，因此我们通常省略它。</p>
<p>现在我们已经为函数指定了类型，下面让我们写出函数的完整类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> myAdd: <span class="function">(<span class="params">x: <span class="built_in">number</span>, y: <span class="built_in">number</span></span>) =&gt;</span> <span class="built_in">number</span> =</span><br><span class="line">    <span class="function"><span class="keyword">function</span>(<span class="params">x: <span class="built_in">number</span>, y: <span class="built_in">number</span></span>): <span class="title">number</span> </span>&#123; <span class="keyword">return</span> x + y; &#125;;</span><br></pre></td></tr></table></figure>

<p>函数类型包含两部分：参数类型和返回值类型。 当写出完整函数类型的时候，这两部分都是需要的。 我们以参数列表的形式写出参数类型，为每个参数指定一个名字和类型。 这个名字只是为了增加可读性。 我们也可以这么写：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> myAdd: <span class="function">(<span class="params">baseValue: <span class="built_in">number</span>, increment: <span class="built_in">number</span></span>) =&gt;</span> <span class="built_in">number</span> =</span><br><span class="line">    <span class="function"><span class="keyword">function</span>(<span class="params">x: <span class="built_in">number</span>, y: <span class="built_in">number</span></span>): <span class="title">number</span> </span>&#123; <span class="keyword">return</span> x + y; &#125;;</span><br></pre></td></tr></table></figure>

<p>只要参数类型是匹配的，那么就认为它是有效的函数类型，而不在乎参数名是否正确。</p>
<p>第二部分是返回值类型。 对于返回值，我们在函数和返回值类型之前使用( <code>=&gt;</code>)符号，使之清晰明了。 如之前提到的，返回值类型是函数类型的必要部分，如果函数没有返回任何值，你也必须指定返回值类型为 <code>void</code>而不能留空。</p>
<p>函数的类型只是由参数类型和返回值组成的。 函数中使用的捕获变量不会体现在类型里。 实际上，这些变量是函数的隐藏状态并不是组成API的一部分。</p>
<p>尝试这个例子的时候，你会发现如果你在赋值语句的一边指定了类型但是另一边没有类型的话，TypeScript编译器会自动识别出类型：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// myAdd has the full function type</span></span><br><span class="line"><span class="keyword">let</span> myAdd = <span class="function"><span class="keyword">function</span>(<span class="params">x: <span class="built_in">number</span>, y: <span class="built_in">number</span></span>): <span class="title">number</span> </span>&#123; <span class="keyword">return</span> x + y; &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// The parameters `x` and `y` have the type number</span></span><br><span class="line"><span class="keyword">let</span> myAdd: <span class="function">(<span class="params">baseValue: <span class="built_in">number</span>, increment: <span class="built_in">number</span></span>) =&gt;</span> <span class="built_in">number</span> =</span><br><span class="line">    <span class="function"><span class="keyword">function</span>(<span class="params">x, y</span>) </span>&#123; <span class="keyword">return</span> x + y; &#125;;</span><br></pre></td></tr></table></figure>

<p>这叫做“按上下文归类”，是类型推论的一种。 它帮助我们更好地为程序指定类型。</p>
<h3 id="可选参数和默认参数"><a href="#可选参数和默认参数" class="headerlink" title="可选参数和默认参数"></a>可选参数和默认参数</h3><p>TypeScript里的每个函数参数都是必须的。 这不是指不能传递 <code>null</code>或<code>undefined</code>作为参数，而是说编译器检查用户是否为每个参数都传入了值。 编译器还会假设只有这些参数会被传递进函数。 简短地说，传递给一个函数的参数个数必须与函数期望的参数个数一致。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">buildName</span>(<span class="params">firstName: <span class="built_in">string</span>, lastName: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> firstName + <span class="string">" "</span> + lastName;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> result1 = buildName(<span class="string">"Bob"</span>);                  <span class="comment">// error, too few parameters</span></span><br><span class="line"><span class="keyword">let</span> result2 = buildName(<span class="string">"Bob"</span>, <span class="string">"Adams"</span>, <span class="string">"Sr."</span>);  <span class="comment">// error, too many parameters</span></span><br><span class="line"><span class="keyword">let</span> result3 = buildName(<span class="string">"Bob"</span>, <span class="string">"Adams"</span>);         <span class="comment">// ah, just right</span></span><br></pre></td></tr></table></figure>

<p>JavaScript里，每个参数都是可选的，可传可不传。 没传参的时候，它的值就是undefined。 在TypeScript里我们可以在参数名旁使用 <code>?</code>实现可选参数的功能。 比如，我们想让last name是可选的：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">buildName</span>(<span class="params">firstName: <span class="built_in">string</span>, lastName?: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (lastName)</span><br><span class="line">        <span class="keyword">return</span> firstName + <span class="string">" "</span> + lastName;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> firstName;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> result1 = buildName(<span class="string">"Bob"</span>);  <span class="comment">// works correctly now</span></span><br><span class="line"><span class="keyword">let</span> result2 = buildName(<span class="string">"Bob"</span>, <span class="string">"Adams"</span>, <span class="string">"Sr."</span>);  <span class="comment">// error, too many parameters</span></span><br><span class="line"><span class="keyword">let</span> result3 = buildName(<span class="string">"Bob"</span>, <span class="string">"Adams"</span>);  <span class="comment">// ah, just right</span></span><br></pre></td></tr></table></figure>

<p>可选参数必须跟在必须参数后面。 如果上例我们想让first name是可选的，那么就必须调整它们的位置，把first name放在后面。</p>
<p>在TypeScript里，我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是<code>undefined</code>时。 它们叫做有默认初始化值的参数。 让我们修改上例，把last name的默认值设置为<code>&quot;Smith&quot;</code>。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">buildName</span>(<span class="params">firstName: <span class="built_in">string</span>, lastName = "Smith"</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> firstName + <span class="string">" "</span> + lastName;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> result1 = buildName(<span class="string">"Bob"</span>);                  <span class="comment">// works correctly now, returns "Bob Smith"</span></span><br><span class="line"><span class="keyword">let</span> result2 = buildName(<span class="string">"Bob"</span>, <span class="literal">undefined</span>);       <span class="comment">// still works, also returns "Bob Smith"</span></span><br><span class="line"><span class="keyword">let</span> result3 = buildName(<span class="string">"Bob"</span>, <span class="string">"Adams"</span>, <span class="string">"Sr."</span>);  <span class="comment">// error, too many parameters</span></span><br><span class="line"><span class="keyword">let</span> result4 = buildName(<span class="string">"Bob"</span>, <span class="string">"Adams"</span>);         <span class="comment">// ah, just right</span></span><br></pre></td></tr></table></figure>

<p>在所有必须参数后面的带默认初始化的参数都是可选的，与可选参数一样，在调用函数的时候可以省略。 也就是说可选参数与末尾的默认参数共享参数类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">buildName</span>(<span class="params">firstName: <span class="built_in">string</span>, lastName?: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>和</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">buildName</span>(<span class="params">firstName: <span class="built_in">string</span>, lastName = "Smith"</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>共享同样的类型<code>(firstName: string, lastName?: string) =&gt; string</code>。 默认参数的默认值消失了，只保留了它是一个可选参数的信息。</p>
<p>与普通可选参数不同的是，带默认值的参数不需要放在必须参数的后面。 如果带默认值的参数出现在必须参数前面，用户必须明确的传入 <code>undefined</code>值来获得默认值。 例如，我们重写最后一个例子，让 <code>firstName</code>是带默认值的参数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">buildName</span>(<span class="params">firstName = "Will", lastName: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> firstName + <span class="string">" "</span> + lastName;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> result1 = buildName(<span class="string">"Bob"</span>);                  <span class="comment">// error, too few parameters</span></span><br><span class="line"><span class="keyword">let</span> result2 = buildName(<span class="string">"Bob"</span>, <span class="string">"Adams"</span>, <span class="string">"Sr."</span>);  <span class="comment">// error, too many parameters</span></span><br><span class="line"><span class="keyword">let</span> result3 = buildName(<span class="string">"Bob"</span>, <span class="string">"Adams"</span>);         <span class="comment">// okay and returns "Bob Adams"</span></span><br><span class="line"><span class="keyword">let</span> result4 = buildName(<span class="literal">undefined</span>, <span class="string">"Adams"</span>);     <span class="comment">// okay and returns "Will Adams"</span></span><br></pre></td></tr></table></figure>

<h3 id="剩余参数"><a href="#剩余参数" class="headerlink" title="剩余参数"></a>剩余参数</h3><p>必要参数，默认参数和可选参数有个共同点：它们表示某一个参数。 有时，你想同时操作多个参数，或者你并不知道会有多少参数传递进来。 在JavaScript里，你可以使用 <code>arguments</code>来访问所有传入的参数。</p>
<p>在TypeScript里，你可以把所有参数收集到一个变量里：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">buildName</span>(<span class="params">firstName: <span class="built_in">string</span>, ...restOfName: <span class="built_in">string</span>[]</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> firstName + <span class="string">" "</span> + restOfName.join(<span class="string">" "</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> employeeName = buildName(<span class="string">"Joseph"</span>, <span class="string">"Samuel"</span>, <span class="string">"Lucas"</span>, <span class="string">"MacKinzie"</span>);</span><br></pre></td></tr></table></figure>

<p>剩余参数会被当做个数不限的可选参数。 可以一个都没有，同样也可以有任意个。 编译器创建参数数组，名字是你在省略号（ <code>...</code>）后面给定的名字，你可以在函数体内使用这个数组。</p>
<p>这个省略号也会在带有剩余参数的函数类型定义上使用到：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">buildName</span>(<span class="params">firstName: <span class="built_in">string</span>, ...restOfName: <span class="built_in">string</span>[]</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> firstName + <span class="string">" "</span> + restOfName.join(<span class="string">" "</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> buildNameFun: <span class="function">(<span class="params">fname: <span class="built_in">string</span>, ...rest: <span class="built_in">string</span>[]</span>) =&gt;</span> <span class="built_in">string</span> = buildName;</span><br></pre></td></tr></table></figure>

<h2 id="this"><a href="#this" class="headerlink" title="this"></a>this</h2><p>学习如何在JavaScript里正确使用<code>this</code>就好比一场成年礼。 由于TypeScript是JavaScript的超集，TypeScript程序员也需要弄清 <code>this</code>工作机制并且当有bug的时候能够找出错误所在。 幸运的是，TypeScript能通知你错误地使用了 <code>this</code>的地方。 如果你想了解JavaScript里的 <code>this</code>是如何工作的，那么首先阅读Yehuda Katz写的<a href="http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/" target="_blank" rel="noopener">Understanding JavaScript Function Invocation and “this”</a>。 Yehuda的文章详细的阐述了 <code>this</code>的内部工作原理，因此我们这里只做简单介绍。</p>
<h3 id="this和箭头函数"><a href="#this和箭头函数" class="headerlink" title="this和箭头函数"></a><code>this</code>和箭头函数</h3><p>JavaScript里，<code>this</code>的值在函数被调用的时候才会指定。 这是个既强大又灵活的特点，但是你需要花点时间弄清楚函数调用的上下文是什么。 但众所周知，这不是一件很简单的事，尤其是在返回一个函数或将函数当做参数传递的时候。</p>
<p>下面看一个例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> deck = &#123;</span><br><span class="line">    suits: [<span class="string">"hearts"</span>, <span class="string">"spades"</span>, <span class="string">"clubs"</span>, <span class="string">"diamonds"</span>],</span><br><span class="line">    cards: <span class="built_in">Array</span>(<span class="number">52</span>),</span><br><span class="line">    createCardPicker: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">            <span class="keyword">let</span> pickedCard = <span class="built_in">Math</span>.floor(<span class="built_in">Math</span>.random() * <span class="number">52</span>);</span><br><span class="line">            <span class="keyword">let</span> pickedSuit = <span class="built_in">Math</span>.floor(pickedCard / <span class="number">13</span>);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> &#123;suit: <span class="keyword">this</span>.suits[pickedSuit], card: pickedCard % <span class="number">13</span>&#125;;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> cardPicker = deck.createCardPicker();</span><br><span class="line"><span class="keyword">let</span> pickedCard = cardPicker();</span><br><span class="line"></span><br><span class="line">alert(<span class="string">"card: "</span> + pickedCard.card + <span class="string">" of "</span> + pickedCard.suit);</span><br></pre></td></tr></table></figure>

<p>可以看到<code>createCardPicker</code>是个函数，并且它又返回了一个函数。 如果我们尝试运行这个程序，会发现它并没有弹出对话框而是报错了。 因为 <code>createCardPicker</code>返回的函数里的<code>this</code>被设置成了<code>window</code>而不是<code>deck</code>对象。 因为我们只是独立的调用了 <code>cardPicker()</code>。 顶级的非方法式调用会将 <code>this</code>视为<code>window</code>。 （注意：在严格模式下， <code>this</code>为<code>undefined</code>而不是<code>window</code>）。</p>
<p>为了解决这个问题，我们可以在函数被返回时就绑好正确的<code>this</code>。 这样的话，无论之后怎么使用它，都会引用绑定的‘deck’对象。 我们需要改变函数表达式来使用ECMAScript 6箭头语法。 <strong>箭头函数能保存函数创建时的 <code>this</code>值，而不是调用时的值：</strong></p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> deck = &#123;</span><br><span class="line">    suits: [<span class="string">"hearts"</span>, <span class="string">"spades"</span>, <span class="string">"clubs"</span>, <span class="string">"diamonds"</span>],</span><br><span class="line">    cards: <span class="built_in">Array</span>(<span class="number">52</span>),</span><br><span class="line">    createCardPicker: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="comment">// <span class="doctag">NOTE:</span> the line below is now an arrow function, allowing us to capture 'this' right here</span></span><br><span class="line">        <span class="keyword">return</span> <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">let</span> pickedCard = <span class="built_in">Math</span>.floor(<span class="built_in">Math</span>.random() * <span class="number">52</span>);</span><br><span class="line">            <span class="keyword">let</span> pickedSuit = <span class="built_in">Math</span>.floor(pickedCard / <span class="number">13</span>);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> &#123;suit: <span class="keyword">this</span>.suits[pickedSuit], card: pickedCard % <span class="number">13</span>&#125;;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> cardPicker = deck.createCardPicker();</span><br><span class="line"><span class="keyword">let</span> pickedCard = cardPicker();</span><br><span class="line"></span><br><span class="line">alert(<span class="string">"card: "</span> + pickedCard.card + <span class="string">" of "</span> + pickedCard.suit);</span><br></pre></td></tr></table></figure>

<p>更好事情是，TypeScript会警告你犯了一个错误，如果你给编译器设置了<code>--noImplicitThis</code>标记。 它会指出 <code>this.suits[pickedSuit]</code>里的<code>this</code>的类型为<code>any</code>。</p>
<h3 id="this参数"><a href="#this参数" class="headerlink" title="this参数"></a><code>this</code>参数</h3><p>不幸的是，<code>this.suits[pickedSuit]</code>的类型依旧为<code>any</code>。 这是因为 <code>this</code>来自对象字面量里的函数表达式。 修改的方法是，提供一个显式的 <code>this</code>参数。 <code>this</code>参数是个假的参数，它出现在参数列表的最前面：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params"><span class="keyword">this</span>: <span class="built_in">void</span></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// make sure `this` is unusable in this standalone function</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>让我们往例子里添加一些接口，<code>Card</code> 和 <code>Deck</code>，让类型重用能够变得清晰简单些：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Card &#123;</span><br><span class="line">    suit: <span class="built_in">string</span>;</span><br><span class="line">    card: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> Deck &#123;</span><br><span class="line">    suits: <span class="built_in">string</span>[];</span><br><span class="line">    cards: <span class="built_in">number</span>[];</span><br><span class="line">    createCardPicker(<span class="keyword">this</span>: Deck): <span class="function"><span class="params">()</span> =&gt;</span> Card;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> deck: Deck = &#123;</span><br><span class="line">    suits: [<span class="string">"hearts"</span>, <span class="string">"spades"</span>, <span class="string">"clubs"</span>, <span class="string">"diamonds"</span>],</span><br><span class="line">    cards: <span class="built_in">Array</span>(<span class="number">52</span>),</span><br><span class="line">    <span class="comment">// <span class="doctag">NOTE:</span> The function now explicitly specifies that its callee must be of type Deck</span></span><br><span class="line">    createCardPicker: <span class="function"><span class="keyword">function</span>(<span class="params"><span class="keyword">this</span>: Deck</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">let</span> pickedCard = <span class="built_in">Math</span>.floor(<span class="built_in">Math</span>.random() * <span class="number">52</span>);</span><br><span class="line">            <span class="keyword">let</span> pickedSuit = <span class="built_in">Math</span>.floor(pickedCard / <span class="number">13</span>);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> &#123;suit: <span class="keyword">this</span>.suits[pickedSuit], card: pickedCard % <span class="number">13</span>&#125;;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> cardPicker = deck.createCardPicker();</span><br><span class="line"><span class="keyword">let</span> pickedCard = cardPicker();</span><br><span class="line"></span><br><span class="line">alert(<span class="string">"card: "</span> + pickedCard.card + <span class="string">" of "</span> + pickedCard.suit);</span><br></pre></td></tr></table></figure>

<p>**现在TypeScript知道<code>createCardPicker</code>期望在某个<code>Deck</code>对象上调用。 也就是说 <code>this</code>是<code>Deck</code>类型的，而非<code>any</code>**，因此<code>--noImplicitThis</code>不会报错了。</p>
<p><code>this</code>参数在回调函数里</p>
<p>你可以也看到过在回调函数里的<code>this</code>报错，当你将一个函数传递到某个库函数里稍后会被调用时。 因为当回调被调用的时候，它们会被当成一个普通函数调用， <code>this</code>将为<code>undefined</code>。 稍做改动，你就可以通过 <code>this</code>参数来避免错误。 首先，库函数的作者要指定 <code>this</code>的类型：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> UIElement &#123;</span><br><span class="line">    addClickListener(onclick: <span class="function">(<span class="params"><span class="keyword">this</span>: <span class="built_in">void</span>, e: Event</span>) =&gt;</span> <span class="built_in">void</span>): <span class="built_in">void</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>this: void</code> means that <code>addClickListener</code> expects <code>onclick</code> to be a function that does not require a <code>this</code> type. Second, annotate your calling code with <code>this</code>:</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Handler &#123;</span><br><span class="line">    info: <span class="built_in">string</span>;</span><br><span class="line">    onClickBad(<span class="keyword">this</span>: Handler, e: Event) &#123;</span><br><span class="line">        <span class="comment">// oops, used this here. using this callback would crash at runtime</span></span><br><span class="line">        <span class="keyword">this</span>.info = e.message;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> h = <span class="keyword">new</span> Handler();</span><br><span class="line">uiElement.addClickListener(h.onClickBad); <span class="comment">// error!</span></span><br></pre></td></tr></table></figure>

<p>指定了<code>this</code>类型后，你显式声明<code>onClickBad</code>必须在<code>Handler</code>的实例上调用。 然后TypeScript会检测到 <code>addClickListener</code>要求函数带有<code>this: void</code>。 改变 <code>this</code>类型来修复这个错误：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Handler &#123;</span><br><span class="line">    info: <span class="built_in">string</span>;</span><br><span class="line">    onClickGood(<span class="keyword">this</span>: <span class="built_in">void</span>, e: Event) &#123;</span><br><span class="line">        <span class="comment">// can't use this here because it's of type void!</span></span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'clicked!'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> h = <span class="keyword">new</span> Handler();</span><br><span class="line">uiElement.addClickListener(h.onClickGood);</span><br></pre></td></tr></table></figure>

<p>因为<code>onClickGood</code>指定了<code>this</code>类型为<code>void</code>，因此传递<code>addClickListener</code>是合法的。 当然了，这也意味着不能使用 <code>this.info</code>. 如果你两者都想要，你不得不使用箭头函数了：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Handler &#123;</span><br><span class="line">    info: <span class="built_in">string</span>;</span><br><span class="line">    onClickGood = <span class="function">(<span class="params">e: Event</span>) =&gt;</span> &#123; <span class="keyword">this</span>.info = e.message &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这是可行的因为箭头函数不会捕获<code>this</code>，所以你总是可以把它们传给期望<code>this: void</code>的函数。 缺点是每个 <code>Handler</code>对象都会创建一个箭头函数。 另一方面，方法只会被创建一次，添加到 <code>Handler</code>的原型链上。 它们在不同 <code>Handler</code>对象间是共享的。</p>
<h3 id="重载"><a href="#重载" class="headerlink" title="重载"></a>重载</h3><p><strong>JavaScript本身是个动态语言。</strong> JavaScript里函数根据传入不同的参数而返回不同类型的数据是很常见的。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> suits = [<span class="string">"hearts"</span>, <span class="string">"spades"</span>, <span class="string">"clubs"</span>, <span class="string">"diamonds"</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">pickCard</span>(<span class="params">x</span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">    <span class="comment">// Check to see if we're working with an object/array</span></span><br><span class="line">    <span class="comment">// if so, they gave us the deck and we'll pick the card</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> x == <span class="string">"object"</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> pickedCard = <span class="built_in">Math</span>.floor(<span class="built_in">Math</span>.random() * x.length);</span><br><span class="line">        <span class="keyword">return</span> pickedCard;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// Otherwise just let them pick the card</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> x == <span class="string">"number"</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> pickedSuit = <span class="built_in">Math</span>.floor(x / <span class="number">13</span>);</span><br><span class="line">        <span class="keyword">return</span> &#123; suit: suits[pickedSuit], card: x % <span class="number">13</span> &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myDeck = [&#123; suit: <span class="string">"diamonds"</span>, card: <span class="number">2</span> &#125;, &#123; suit: <span class="string">"spades"</span>, card: <span class="number">10</span> &#125;, &#123; suit: <span class="string">"hearts"</span>, card: <span class="number">4</span> &#125;];</span><br><span class="line"><span class="keyword">let</span> pickedCard1 = myDeck[pickCard(myDeck)];</span><br><span class="line">alert(<span class="string">"card: "</span> + pickedCard1.card + <span class="string">" of "</span> + pickedCard1.suit);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> pickedCard2 = pickCard(<span class="number">15</span>);</span><br><span class="line">alert(<span class="string">"card: "</span> + pickedCard2.card + <span class="string">" of "</span> + pickedCard2.suit);</span><br></pre></td></tr></table></figure>

<p><code>pickCard</code>方法根据传入参数的不同会返回两种不同的类型。 如果传入的是代表纸牌的对象，函数作用是从中抓一张牌。 如果用户想抓牌，我们告诉他抓到了什么牌。 但是这怎么在类型系统里表示呢。</p>
<p>方法是为同一个函数提供多个函数类型定义来进行函数重载。 编译器会根据这个列表去处理函数的调用。 下面我们来重载 <code>pickCard</code>函数。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> suits = [<span class="string">"hearts"</span>, <span class="string">"spades"</span>, <span class="string">"clubs"</span>, <span class="string">"diamonds"</span>];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">pickCard</span>(<span class="params">x: &#123;suit: <span class="built_in">string</span>; card: <span class="built_in">number</span>; &#125;[]</span>): <span class="title">number</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">pickCard</span>(<span class="params">x: <span class="built_in">number</span></span>): </span>&#123;suit: <span class="built_in">string</span>; card: <span class="built_in">number</span>; &#125;;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">pickCard</span>(<span class="params">x</span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">    <span class="comment">// Check to see if we're working with an object/array</span></span><br><span class="line">    <span class="comment">// if so, they gave us the deck and we'll pick the card</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> x == <span class="string">"object"</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> pickedCard = <span class="built_in">Math</span>.floor(<span class="built_in">Math</span>.random() * x.length);</span><br><span class="line">        <span class="keyword">return</span> pickedCard;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// Otherwise just let them pick the card</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> x == <span class="string">"number"</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> pickedSuit = <span class="built_in">Math</span>.floor(x / <span class="number">13</span>);</span><br><span class="line">        <span class="keyword">return</span> &#123; suit: suits[pickedSuit], card: x % <span class="number">13</span> &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myDeck = [&#123; suit: <span class="string">"diamonds"</span>, card: <span class="number">2</span> &#125;, &#123; suit: <span class="string">"spades"</span>, card: <span class="number">10</span> &#125;, &#123; suit: <span class="string">"hearts"</span>, card: <span class="number">4</span> &#125;];</span><br><span class="line"><span class="keyword">let</span> pickedCard1 = myDeck[pickCard(myDeck)];</span><br><span class="line">alert(<span class="string">"card: "</span> + pickedCard1.card + <span class="string">" of "</span> + pickedCard1.suit);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> pickedCard2 = pickCard(<span class="number">15</span>);</span><br><span class="line">alert(<span class="string">"card: "</span> + pickedCard2.card + <span class="string">" of "</span> + pickedCard2.suit);</span><br></pre></td></tr></table></figure>

<p>这样改变后，重载的<code>pickCard</code>函数在调用的时候会进行正确的类型检查。</p>
<p>为了让编译器能够选择正确的检查类型，它与JavaScript里的处理流程相似。 它查找重载列表，尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此，在定义重载的时候，一定要把最精确的定义放在最前面。</p>
<p>注意，<code>function pickCard(x): any</code>并不是重载列表的一部分，因此这里只有两个重载：一个是接收对象另一个接收数字。 以其它参数调用 <code>pickCard</code>会产生错误。</p>
<h2 id="泛型"><a href="#泛型" class="headerlink" title="泛型"></a>泛型</h2><p>软件工程中，我们不仅要创建一致的定义良好的API，同时也要考虑可重用性。 组件不仅能够支持当前的数据类型，同时也能支持未来的数据类型，这在创建大型系统时为你提供了十分灵活的功能。</p>
<p>在像C#和Java这样的语言中，可以使用<code>泛型</code>来创建可重用的组件，一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。</p>
<h3 id="泛型之Hello-World"><a href="#泛型之Hello-World" class="headerlink" title="泛型之Hello World"></a>泛型之Hello World</h3><p>下面来创建第一个使用泛型的例子：identity函数。 这个函数会返回任何传入它的值。 你可以把这个函数当成是 <code>echo</code>命令。</p>
<p>不用泛型的话，这个函数可能是下面这样：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">identity</span>(<span class="params">arg: <span class="built_in">number</span></span>): <span class="title">number</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>或者，我们使用<code>any</code>类型来定义函数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">identity</span>(<span class="params">arg: <span class="built_in">any</span></span>): <span class="title">any</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>使用<code>any</code>类型会导致这个函数可以接收任何类型的<code>arg</code>参数，这样就丢失了一些信息：传入的类型与返回的类型应该是相同的。如果我们传入一个数字，我们只知道任何类型的值都有可能被返回。</p>
<p>因此，我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里，我们使用了 <em>类型变量</em>，它是一种特殊的变量，只用于表示类型而不是值。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">identity</span>&lt;<span class="title">T</span>&gt;(<span class="params">arg: T</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们给identity添加了类型变量<code>T</code>。 <strong><code>T</code>帮助我们捕获用户传入的类型（比如：<code>number</code>），之后我们就可以使用这个类型。 之后我们再次使用了 <code>T</code>当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。</strong></p>
<p>我们把这个版本的<code>identity</code>函数叫做泛型，因为它可以适用于多个类型。 不同于使用 <code>any</code>，它不会丢失信息，像第一个例子那像保持准确性，传入数值类型并返回数值类型。</p>
<p>我们定义了泛型函数后，可以用两种方法使用。 第一种是，传入所有的参数，包含类型参数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> output = identity&lt;<span class="built_in">string</span>&gt;(<span class="string">"myString"</span>);  <span class="comment">// type of output will be 'string'</span></span><br></pre></td></tr></table></figure>

<p>这里我们明确的指定了<code>T</code>是<code>string</code>类型，并做为一个参数传给函数，使用了<code>&lt;&gt;</code>括起来而不是<code>()</code>。</p>
<p>第二种方法更普遍。利用了<em>类型推论</em> – 即编译器会根据传入的参数自动地帮助我们确定T的类型：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> output = identity(<span class="string">"myString"</span>);  <span class="comment">// type of output will be 'string'</span></span><br></pre></td></tr></table></figure>

<p>注意我们没必要使用尖括号（<code>&lt;&gt;</code>）来明确地传入类型；编译器可以查看<code>myString</code>的值，然后把<code>T</code>设置为它的类型。 类型推论帮助我们保持代码精简和高可读性。如果编译器不能够自动地推断出类型的话，只能像上面那样明确的传入T的类型，在一些复杂的情况下，这是可能出现的。</p>
<h3 id="使用泛型变量"><a href="#使用泛型变量" class="headerlink" title="使用泛型变量"></a>使用泛型变量</h3><p>使用泛型创建像<code>identity</code>这样的泛型函数时，编译器要求你在函数体必须正确的使用这个通用的类型。 换句话说，你必须把这些参数当做是任意或所有类型。</p>
<p>看下之前<code>identity</code>例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">identity</span>&lt;<span class="title">T</span>&gt;(<span class="params">arg: T</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果我们想同时打印出<code>arg</code>的长度。 我们很可能会这样做：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">loggingIdentity</span>&lt;<span class="title">T</span>&gt;(<span class="params">arg: T</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(arg.length);  <span class="comment">// Error: T doesn't have .length</span></span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果这么做，编译器会报错说我们使用了<code>arg</code>的<code>.length</code>属性，但是没有地方指明<code>arg</code>具有这个属性。 记住，这些类型变量代表的是任意类型，所以使用这个函数的人可能传入的是个数字，而数字是没有 <code>.length</code>属性的。</p>
<p>现在假设我们想操作<code>T</code>类型的数组而不直接是<code>T</code>。由于我们操作的是数组，所以<code>.length</code>属性是应该存在的。 我们可以像创建其它数组一样创建这个数组：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">loggingIdentity</span>&lt;<span class="title">T</span>&gt;(<span class="params">arg: T[]</span>): <span class="title">T</span>[] </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(arg.length);  <span class="comment">// Array has a .length, so no more error</span></span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>你可以这样理解<code>loggingIdentity</code>的类型：<strong>泛型函数<code>loggingIdentity</code>，接收类型参数<code>T</code>和参数<code>arg</code>，它是个元素类型是<code>T</code>的数组，并返回元素类型是<code>T</code>的数组。</strong> 如果我们传入数字数组，将返回一个数字数组，因为此时 <code>T</code>的的类型为<code>number</code>。 这可以让我<strong>们把泛型变量T当做类型的一部分使用</strong>，而不是整个类型，增加了灵活性。</p>
<p>我们也可以这样实现上面的例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">loggingIdentity</span>&lt;<span class="title">T</span>&gt;(<span class="params">arg: <span class="built_in">Array</span>&lt;T&gt;</span>): <span class="title">Array</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(arg.length);  <span class="comment">// Array has a .length, so no more error</span></span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>使用过其它语言的话，你可能对这种语法已经很熟悉了。 在下一节，会介绍如何创建自定义泛型像 <code>Array&lt;T&gt;</code>一样。</p>
<h3 id="泛型类型"><a href="#泛型类型" class="headerlink" title="泛型类型"></a>泛型类型</h3><p>上一节，我们创建了identity通用函数，可以适用于不同的类型。 在这节，我们研究一下函数本身的类型，以及如何创建泛型接口。</p>
<p>泛型函数的类型与非泛型函数的类型没什么不同， 只是有一个类型参数在最前面，像函数声明一样：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">identity</span>&lt;<span class="title">T</span>&gt;(<span class="params">arg: T</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myIdentity: &lt;T&gt;<span class="function">(<span class="params">arg: T</span>) =&gt;</span> T = identity;</span><br></pre></td></tr></table></figure>

<p>我们也可以使用不同的泛型参数名，只要在数量上和使用方式上能对应上就可以。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">identity</span>&lt;<span class="title">T</span>&gt;(<span class="params">arg: T</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myIdentity: &lt;U&gt;<span class="function">(<span class="params">arg: U</span>) =&gt;</span> U = identity;</span><br></pre></td></tr></table></figure>

<p>我们还可以使用带有调用签名的对象字面量来定义泛型函数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">identity</span>&lt;<span class="title">T</span>&gt;(<span class="params">arg: T</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myIdentity: &#123;&lt;T&gt;(arg: T): T&#125; = identity;</span><br></pre></td></tr></table></figure>

<p>这引导我们去写第一个泛型接口了。 我们把上面例子里的对象字面量拿出来做为一个接口：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> GenericIdentityFn &#123;</span><br><span class="line">    &lt;T&gt;(arg: T): T;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">identity</span>&lt;<span class="title">T</span>&gt;(<span class="params">arg: T</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myIdentity: GenericIdentityFn = identity;</span><br></pre></td></tr></table></figure>

<p>一个相似的例子，我们可能想把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型（比如： <code>Dictionary&lt;string&gt;而不只是Dictionary</code>）。 这样接口里的其它成员也能知道这个参数的类型了。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> GenericIdentityFn&lt;T&gt; &#123;</span><br><span class="line">    (arg: T): T;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">identity</span>&lt;<span class="title">T</span>&gt;(<span class="params">arg: T</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myIdentity: GenericIdentityFn&lt;<span class="built_in">number</span>&gt; = identity;</span><br></pre></td></tr></table></figure>

<p>注意，我们的示例做了少许改动。 不再描述泛型函数，而是把非泛型函数签名作为泛型类型一部分。 当我们使用 <code>GenericIdentityFn</code>的时候，还得传入一个类型参数来指定泛型类型（这里是：<code>number</code>），锁定了之后代码里使用的类型。 对于描述哪部分类型属于泛型部分来说，理解何时把参数放在调用签名里和何时放在接口上是很有帮助的。</p>
<p>除了泛型接口，我们还可以创建泛型类。 注意，无法创建泛型枚举和泛型命名空间。</p>
<h3 id="泛型类"><a href="#泛型类" class="headerlink" title="泛型类"></a>泛型类</h3><p>泛型类看上去与泛型接口差不多。 泛型类使用（ <code>&lt;&gt;</code>）括起泛型类型，跟在类名后面。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> GenericNumber&lt;T&gt; &#123;</span><br><span class="line">    zeroValue: T;</span><br><span class="line">    add: <span class="function">(<span class="params">x: T, y: T</span>) =&gt;</span> T;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myGenericNumber = <span class="keyword">new</span> GenericNumber&lt;<span class="built_in">number</span>&gt;();</span><br><span class="line">myGenericNumber.zeroValue = <span class="number">0</span>;</span><br><span class="line">myGenericNumber.add = <span class="function"><span class="keyword">function</span>(<span class="params">x, y</span>) </span>&#123; <span class="keyword">return</span> x + y; &#125;;</span><br></pre></td></tr></table></figure>

<p><code>GenericNumber</code>类的使用是十分直观的，并且你可能已经注意到了，没有什么去限制它只能使用<code>number</code>类型。 也可以使用字符串或其它更复杂的类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> stringNumeric = <span class="keyword">new</span> GenericNumber&lt;<span class="built_in">string</span>&gt;();</span><br><span class="line">stringNumeric.zeroValue = <span class="string">""</span>;</span><br><span class="line">stringNumeric.add = <span class="function"><span class="keyword">function</span>(<span class="params">x, y</span>) </span>&#123; <span class="keyword">return</span> x + y; &#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(stringNumeric.add(stringNumeric.zeroValue, <span class="string">"test"</span>));</span><br></pre></td></tr></table></figure>

<p>与接口一样，直接把泛型类型放在类后面，可以帮助我们确认类的所有属性都在使用相同的类型。</p>
<p>我们在<a href="https://www.tslang.cn/docs/handbook/classes.html" target="_blank" rel="noopener">类</a>那节说过，类有两部分：静态部分和实例部分。 泛型类指的是实例部分的类型，所以类的静态属性不能使用这个泛型类型。</p>
<h3 id="泛型约束"><a href="#泛型约束" class="headerlink" title="泛型约束"></a>泛型约束</h3><p>你应该会记得之前的一个例子，我们有时候想操作某类型的一组值，并且我们知道这组值具有什么样的属性。 在 <code>loggingIdentity</code>例子中，我们想访问<code>arg</code>的<code>length</code>属性，但是编译器并不能证明每种类型都有<code>length</code>属性，所以就报错了。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">loggingIdentity</span>&lt;<span class="title">T</span>&gt;(<span class="params">arg: T</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(arg.length);  <span class="comment">// Error: T doesn't have .length</span></span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>相比于操作any所有类型，我们想要限制函数去处理任意带有<code>.length</code>属性的所有类型。 只要传入的类型有这个属性，我们就允许，就是说至少包含这一属性。 为此，我们需要列出对于T的约束要求。</p>
<p><strong>为此，我们定义一个接口来描述约束条件。 创建一个包含 <code>.length</code>属性的接口，使用这个接口和<code>extends</code>关键字来实现约束：</strong></p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Lengthwise &#123;</span><br><span class="line">    length: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">loggingIdentity</span>&lt;<span class="title">T</span> <span class="title">extends</span> <span class="title">Lengthwise</span>&gt;(<span class="params">arg: T</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(arg.length);  <span class="comment">// Now we know it has a .length property, so no more error</span></span><br><span class="line">    <span class="keyword">return</span> arg;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>现在这个泛型函数被定义了约束，因此它不再是适用于任意类型：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">loggingIdentity(<span class="number">3</span>);  <span class="comment">// Error, number doesn't have a .length property</span></span><br></pre></td></tr></table></figure>

<p>我们需要传入符合约束类型的值，必须包含必须的属性：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">loggingIdentity(&#123;length: <span class="number">10</span>, value: <span class="number">3</span>&#125;);</span><br></pre></td></tr></table></figure>

<p><strong>在泛型约束中使用类型参数：</strong></p>
<p>你可以声明一个类型参数，且它被另一个类型参数所约束。 比如，现在我们想要用属性名从对象里获取这个属性。 并且我们想要确保这个属性存在于对象 <code>obj</code>上，因此我们需要在这两个类型之间使用约束。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getProperty</span>(<span class="params">obj: T, key: K</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> obj[key];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x = &#123; a: <span class="number">1</span>, b: <span class="number">2</span>, c: <span class="number">3</span>, d: <span class="number">4</span> &#125;;</span><br><span class="line"></span><br><span class="line">getProperty(x, <span class="string">"a"</span>); <span class="comment">// okay</span></span><br><span class="line">getProperty(x, <span class="string">"m"</span>); <span class="comment">// error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.</span></span><br></pre></td></tr></table></figure>

<p><strong>在泛型里使用类类型：</strong></p>
<p>在TypeScript使用泛型创建工厂函数时，需要引用构造函数的类类型。比如，</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">create</span>&lt;<span class="title">T</span>&gt;(<span class="params">c: &#123;<span class="keyword">new</span>(): T; &#125;</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> c();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>一个更高级的例子，使用原型属性推断并约束构造函数与类实例的关系。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> BeeKeeper &#123;</span><br><span class="line">    hasMask: <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> ZooKeeper &#123;</span><br><span class="line">    nametag: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Animal &#123;</span><br><span class="line">    numLegs: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Bee <span class="keyword">extends</span> Animal &#123;</span><br><span class="line">    keeper: BeeKeeper;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Lion <span class="keyword">extends</span> Animal &#123;</span><br><span class="line">    keeper: ZooKeeper;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createInstance</span>&lt;<span class="title">A</span> <span class="title">extends</span> <span class="title">Animal</span>&gt;(<span class="params">c: <span class="keyword">new</span> () =&gt; A</span>): <span class="title">A</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> c();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">createInstance(Lion).keeper.nametag;  <span class="comment">// typechecks!</span></span><br><span class="line">createInstance(Bee).keeper.hasMask;   <span class="comment">// typechecks!</span></span><br></pre></td></tr></table></figure>

<h2 id="枚举-1"><a href="#枚举-1" class="headerlink" title="枚举"></a>枚举</h2><p>使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript支持数字的和基于字符串的枚举。</p>
<p>首先我们看看数字枚举，如果你使用过其它编程语言应该会很熟悉。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> Direction &#123;</span><br><span class="line">    Up = <span class="number">1</span>,</span><br><span class="line">    Down,</span><br><span class="line">    Left,</span><br><span class="line">    Right</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如上，我们定义了一个数字枚举， <code>Up</code>使用初始化为 <code>1</code>。 其余的成员会从 <code>1</code>开始自动增长。 换句话说， <code>Direction.Up</code>的值为 <code>1</code>， <code>Down</code>为 <code>2</code>， <code>Left</code>为 <code>3</code>， <code>Right</code>为 <code>4</code>。</p>
<p>我们还可以完全不使用初始化器：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> Direction &#123;</span><br><span class="line">    Up,</span><br><span class="line">    Down,</span><br><span class="line">    Left,</span><br><span class="line">    Right,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>现在， <code>Up</code>的值为 <code>0</code>， <code>Down</code>的值为 <code>1</code>等等。 当我们不在乎成员的值的时候，这种自增长的行为是很有用处的，但是要注意每个枚举成员的值都是不同的。</p>
<p>使用枚举很简单：通过枚举的属性来访问枚举成员，和枚举的名字来访问枚举类型：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> Response &#123;</span><br><span class="line">    No = <span class="number">0</span>,</span><br><span class="line">    Yes = <span class="number">1</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">respond</span>(<span class="params">recipient: <span class="built_in">string</span>, message: Response</span>): <span class="title">void</span> </span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">respond(<span class="string">"Princess Caroline"</span>, Response.Yes)</span><br></pre></td></tr></table></figure>

<p>数字枚举可以被混入到 <a href="https://www.tslang.cn/docs/handbook/enums.html#computed-and-constant-members" target="_blank" rel="noopener">计算过的和常量成员（如下所示）</a>。 简短地说，不带初始化器的枚举或者被放在第一的位置，或者被放在使用了数字常量或其它常量初始化了的枚举后面。 换句话说，下面的情况是不被允许的：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> E &#123;</span><br><span class="line">    A = getSomeValue(),</span><br><span class="line">    B, <span class="comment">// error! 'A' is not constant-initialized, so 'B' needs an initializer</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>字符串枚举的概念很简单，但是有细微的 <a href="https://www.tslang.cn/docs/handbook/enums.html#enums-at-runtime" target="_blank" rel="noopener">运行时的差别</a>。 在一个字符串枚举里，每个成员都必须用字符串字面量，或另外一个字符串枚举成员进行初始化。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> Direction &#123;</span><br><span class="line">    Up = <span class="string">"UP"</span>,</span><br><span class="line">    Down = <span class="string">"DOWN"</span>,</span><br><span class="line">    Left = <span class="string">"LEFT"</span>,</span><br><span class="line">    Right = <span class="string">"RIGHT"</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>由于字符串枚举没有自增长的行为，字符串枚举可以很好的序列化。 换句话说，如果你正在调试并且必须要读一个数字枚举的运行时的值，这个值通常是很难读的 - 它并不能表达有用的信息（尽管 <a href="https://www.tslang.cn/docs/handbook/enums.html#enums-at-runtime" target="_blank" rel="noopener">反向映射</a>会有所帮助），字符串枚举允许你提供一个运行时有意义的并且可读的值，独立于枚举成员的名字。</p>
<p>异构枚举</p>
<p>从技术的角度来说，枚举可以混合字符串和数字成员，但是似乎你并不会这么做：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> BooleanLikeHeterogeneousEnum &#123;</span><br><span class="line">    No = <span class="number">0</span>,</span><br><span class="line">    Yes = <span class="string">"YES"</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>除非你真的想要利用JavaScript运行时的行为，否则我们不建议这样做。</p>
<h3 id="计算和常量成员"><a href="#计算和常量成员" class="headerlink" title="计算和常量成员"></a>计算和常量成员</h3><p>每个枚举成员都带有一个值，它可以是 <em>常量</em>或 <em>计算出来的</em>。 当满足如下条件时，枚举成员被当作是常量：</p>
<ul>
<li><p>它是枚举的第一个成员且没有初始化器，这种情况下它被赋予值 <code>0</code>：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// E.X is constant:</span></span><br><span class="line"><span class="keyword">enum</span> E &#123; X &#125;</span><br></pre></td></tr></table></figure></li>
<li><p>它不带有初始化器且它之前的枚举成员是一个 <em>数字</em>常量。 这种情况下，当前枚举成员的值为它上一个枚举成员的值加1。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// All enum members in 'E1' and 'E2' are constant.</span></span><br><span class="line"><span class="keyword">enum</span> E1 &#123; X, Y, Z &#125;</span><br><span class="line"><span class="keyword">enum</span> E2 &#123;</span><br><span class="line">    A = <span class="number">1</span>, B, C</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li><p>枚举成员使用常量枚举表达式初始化。 常数枚举表达式是TypeScript表达式的子集，它可以在编译阶段求值。 当一个表达式满足下面条件之一时，它就是一个常量枚举表达式：</p>
<ul>
<li>一个枚举表达式字面量（主要是字符串字面量或数字字面量）</li>
<li>一个对之前定义的常量枚举成员的引用（可以是在不同的枚举类型中定义的）</li>
<li>带括号的常量枚举表达式</li>
<li>一元运算符 <code>+</code>, <code>-</code>, <code>~</code>其中之一应用在了常量枚举表达式</li>
<li>常量枚举表达式做为二元运算符 <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>%</code>, <code>&lt;&lt;</code>, <code>&gt;&gt;</code>, <code>&gt;&gt;&gt;</code>, <code>&amp;</code>, <code>|</code>, <code>^</code>的操作对象。 若常数枚举表达式求值后为 <code>NaN</code>或 <code>Infinity</code>，则会在编译阶段报错。</li>
</ul>
</li>
</ul>
<p>所有其它情况的枚举成员被当作是需要计算得出的值。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> FileAccess &#123;</span><br><span class="line">    <span class="comment">// constant members</span></span><br><span class="line">    None,</span><br><span class="line">    Read    = <span class="number">1</span> &lt;&lt; <span class="number">1</span>,</span><br><span class="line">    Write   = <span class="number">1</span> &lt;&lt; <span class="number">2</span>,</span><br><span class="line">    ReadWrite  = Read | Write,</span><br><span class="line">    <span class="comment">// computed member</span></span><br><span class="line">    G = <span class="string">"123"</span>.length</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="联合枚举与枚举成员的类型"><a href="#联合枚举与枚举成员的类型" class="headerlink" title="联合枚举与枚举成员的类型"></a>联合枚举与枚举成员的类型</h3><p>存在一种特殊的非计算的常量枚举成员的子集：字面量枚举成员。 字面量枚举成员是指不带有初始值的常量枚举成员，或者是值被初始化为</p>
<ul>
<li>任何字符串字面量（例如： <code>&quot;foo&quot;</code>， <code>&quot;bar&quot;</code>， <code>&quot;baz&quot;</code>）</li>
<li>任何数字字面量（例如： <code>1</code>, <code>100</code>）</li>
<li>应用了一元 <code>-</code>符号的数字字面量（例如： <code>-1</code>, <code>-100</code>）</li>
</ul>
<p>当所有枚举成员都拥有字面量枚举值时，它就带有了一种特殊的语义。</p>
<p>首先，枚举成员成为了类型！ 例如，我们可以说某些成员 <em>只能</em>是枚举成员的值：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> ShapeKind &#123;</span><br><span class="line">    Circle,</span><br><span class="line">    Square,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Circle &#123;</span><br><span class="line">    kind: ShapeKind.Circle;</span><br><span class="line">    radius: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Square &#123;</span><br><span class="line">    kind: ShapeKind.Square;</span><br><span class="line">    sideLength: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> c: Circle = &#123;</span><br><span class="line">    kind: ShapeKind.Square,</span><br><span class="line">    <span class="comment">//    ~~~~~~~~~~~~~~~~ Error!</span></span><br><span class="line">    radius: <span class="number">100</span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>另一个变化是枚举类型本身变成了每个枚举成员的 <em>联合</em>。 虽然我们还没有讨论[联合类型](./Advanced Types.md#union-types)，但你只要知道通过联合枚举，类型系统能够利用这样一个事实，它可以知道枚举里的值的集合。 因此，TypeScript能够捕获在比较值的时候犯的愚蠢的错误。 例如：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> E &#123;</span><br><span class="line">    Foo,</span><br><span class="line">    Bar,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params">x: E</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (x !== E.Foo || x !== E.Bar) &#123;</span><br><span class="line">        <span class="comment">//             ~~~~~~~~~~~</span></span><br><span class="line">        <span class="comment">// Error! Operator '!==' cannot be applied to types 'E.Foo' and 'E.Bar'.</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这个例子里，我们先检查 <code>x</code>是否不是 <code>E.Foo</code>。 如果通过了这个检查，然后 <code>||</code>会发生短路效果， <code>if</code>语句体里的内容会被执行。 然而，这个检查没有通过，那么 <code>x</code>则 <em>只能</em>为 <code>E.Foo</code>，因此没理由再去检查它是否为 <code>E.Bar</code>。</p>
<h3 id="运行时的枚举"><a href="#运行时的枚举" class="headerlink" title="运行时的枚举"></a>运行时的枚举</h3><p>枚举是在运行时真正存在的对象。 例如下面的枚举：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> E &#123;</span><br><span class="line">    X, Y, Z</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>can actually be passed around to functions</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params">obj: &#123; X: <span class="built_in">number</span> &#125;</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> obj.X;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Works, since 'E' has a property named 'X' which is a number.</span></span><br><span class="line">f(E);</span><br></pre></td></tr></table></figure>

<h3 id="反向映射"><a href="#反向映射" class="headerlink" title="反向映射"></a>反向映射</h3><p>除了创建一个以属性名做为对象成员的对象之外，数字枚举成员还具有了 <em>反向映射</em>，从枚举值到枚举名字。 例如，在下面的例子中：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> Enum &#123;</span><br><span class="line">    A</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> a = Enum.A;</span><br><span class="line"><span class="keyword">let</span> nameOfA = Enum[a]; <span class="comment">// "A"</span></span><br></pre></td></tr></table></figure>

<p>TypeScript可能会将这段代码编译为下面的JavaScript：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Enum;</span><br><span class="line">(<span class="function"><span class="keyword">function</span> (<span class="params">Enum</span>) </span>&#123;</span><br><span class="line">    Enum[Enum[<span class="string">"A"</span>] = <span class="number">0</span>] = <span class="string">"A"</span>;</span><br><span class="line">&#125;)(Enum || (Enum = &#123;&#125;));</span><br><span class="line"><span class="keyword">var</span> a = Enum.A;</span><br><span class="line"><span class="keyword">var</span> nameOfA = Enum[a]; <span class="comment">// "A"</span></span><br></pre></td></tr></table></figure>

<p>生成的代码中，枚举类型被编译成一个对象，<strong>它包含了正向映射（ <code>name</code> -&gt; <code>value</code>）和反向映射（ <code>value</code> -&gt; <code>name</code>）。 引用枚举成员总会生成为对属性访问并且永远也不会内联代码。</strong></p>
<p>要注意的是 <em>不会</em>为字符串枚举成员生成反向映射。</p>
<p><code>const</code>枚举：</p>
<p>大多数情况下，枚举是十分有效的方案。 然而在某些情况下需求很严格。 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问，我们可以使用 <code>const</code>枚举。 常量枚举通过在枚举上使用 <code>const</code>修饰符来定义。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="keyword">enum</span> Enum &#123;</span><br><span class="line">    A = <span class="number">1</span>,</span><br><span class="line">    B = A * <span class="number">2</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>常量枚举只能使用常量枚举表达式，并且不同于常规的枚举，它们在编译阶段会被删除。 常量枚举成员在使用的地方会被内联进来。 之所以可以这么做是因为，常量枚举不允许包含计算成员。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="keyword">enum</span> Directions &#123;</span><br><span class="line">    Up,</span><br><span class="line">    Down,</span><br><span class="line">    Left,</span><br><span class="line">    Right</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]</span><br></pre></td></tr></table></figure>

<p>生成后的代码为：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> directions = [<span class="number">0</span> <span class="comment">/* Up */</span>, <span class="number">1</span> <span class="comment">/* Down */</span>, <span class="number">2</span> <span class="comment">/* Left */</span>, <span class="number">3</span> <span class="comment">/* Right */</span>];</span><br></pre></td></tr></table></figure>

<h3 id="外部枚举"><a href="#外部枚举" class="headerlink" title="外部枚举"></a>外部枚举</h3><p>外部枚举用来描述已经存在的枚举类型的形状。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">enum</span> Enum &#123;</span><br><span class="line">    A = <span class="number">1</span>,</span><br><span class="line">    B,</span><br><span class="line">    C = <span class="number">2</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>外部枚举和非外部枚举之间有一个重要的区别，在正常的枚举里，没有初始化方法的成员被当成常数成员。 对于非常数的外部枚举而言，没有初始化方法时被当做需要经过计算的。</p>
<h2 id="类型推论与兼容"><a href="#类型推论与兼容" class="headerlink" title="类型推论与兼容"></a>类型推论与兼容</h2><h3 id="类型推论"><a href="#类型推论" class="headerlink" title="类型推论"></a>类型推论</h3><p>TypeScript里，在有些没有明确指出类型的地方，类型推论会帮助提供类型。如下面的例子</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = <span class="number">3</span>;</span><br></pre></td></tr></table></figure>

<p>变量<code>x</code>的类型被推断为数字。 这种推断发生在初始化变量和成员，设置默认参数值和决定函数返回值时。</p>
<p>大多数情况下，类型推论是直截了当地。 后面的小节，我们会浏览类型推论时的细微差别。</p>
<h4 id="最佳通用类型"><a href="#最佳通用类型" class="headerlink" title="最佳通用类型"></a>最佳通用类型</h4><p>当需要从几个表达式中推断类型时候，会使用这些表达式的类型来推断出一个最合适的通用类型。例如，</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = [<span class="number">0</span>, <span class="number">1</span>, <span class="literal">null</span>];</span><br></pre></td></tr></table></figure>

<p>为了推断<code>x</code>的类型，我们必须考虑所有元素的类型。 这里有两种选择： <code>number</code>和<code>null</code>。 计算通用类型算法会考虑所有的候选类型，并给出一个兼容所有候选类型的类型。</p>
<p>由于最终的通用类型取自候选类型，有些时候候选类型共享相同的通用类型，但是却没有一个类型能做为所有候选类型的类型。例如：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> zoo = [<span class="keyword">new</span> Rhino(), <span class="keyword">new</span> Elephant(), <span class="keyword">new</span> Snake()];</span><br></pre></td></tr></table></figure>

<p>这里，我们想让zoo被推断为<code>Animal[]</code>类型，但是这个数组里没有对象是<code>Animal</code>类型的，因此不能推断出这个结果。 为了更正，当候选类型不能使用的时候我们需要明确的指出类型：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> zoo: Animal[] = [<span class="keyword">new</span> Rhino(), <span class="keyword">new</span> Elephant(), <span class="keyword">new</span> Snake()];</span><br></pre></td></tr></table></figure>

<p>如果没有找到最佳通用类型的话，类型推断的结果为联合数组类型，<code>(Rhino | Elephant | Snake)[]</code>。</p>
<h4 id="上下文类型"><a href="#上下文类型" class="headerlink" title="上下文类型"></a>上下文类型</h4><p>TypeScript类型推论也可能按照相反的方向进行。 这被叫做“按上下文归类”。按上下文归类会发生在表达式的类型与所处的位置相关时。比如：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.onmousedown = <span class="function"><span class="keyword">function</span>(<span class="params">mouseEvent</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(mouseEvent.button);  <span class="comment">//&lt;- Error</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>这个例子会得到一个类型错误，TypeScript类型检查器使用<code>Window.onmousedown</code>函数的类型来推断右边函数表达式的类型。 因此，就能推断出 <code>mouseEvent</code>参数的类型了。 如果函数表达式不是在上下文类型的位置， <code>mouseEvent</code>参数的类型需要指定为<code>any</code>，这样也不会报错了。</p>
<p>如果上下文类型表达式包含了明确的类型信息，上下文的类型被忽略。 重写上面的例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.onmousedown = <span class="function"><span class="keyword">function</span>(<span class="params">mouseEvent: <span class="built_in">any</span></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(mouseEvent.button);  <span class="comment">//&lt;- Now, no error is given</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>这个函数表达式有明确的参数类型注解，上下文类型被忽略。 这样的话就不报错了，因为这里不会使用到上下文类型。</p>
<p>上下文归类会在很多情况下使用到。 通常包含函数的参数，赋值表达式的右边，类型断言，对象成员和数组字面量和返回值语句。 上下文类型也会做为最佳通用类型的候选类型。比如：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createZoo</span>(<span class="params"></span>): <span class="title">Animal</span>[] </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> [<span class="keyword">new</span> Rhino(), <span class="keyword">new</span> Elephant(), <span class="keyword">new</span> Snake()];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这个例子里，最佳通用类型有4个候选者：<code>Animal</code>，<code>Rhino</code>，<code>Elephant</code>和<code>Snake</code>。 当然， <code>Animal</code>会被做为最佳通用类型。</p>
<h3 id="类型兼容性"><a href="#类型兼容性" class="headerlink" title="类型兼容性"></a>类型兼容性</h3><p>TypeScript里的类型兼容性是基于结构子类型的。 结构类型是一种只使用其成员来描述类型的方式。 它正好与名义（nominal）类型形成对比。（译者注：在基于名义类型的类型系统中，数据类型的兼容性或等价性是通过明确的声明和/或类型的名称来决定的。这与结构性类型系统不同，它是基于类型的组成结构，且不要求明确地声明。） 看下面的例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Named &#123;</span><br><span class="line">    name: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Person &#123;</span><br><span class="line">    name: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> p: Named;</span><br><span class="line"><span class="comment">// OK, because of structural typing</span></span><br><span class="line">p = <span class="keyword">new</span> Person();</span><br></pre></td></tr></table></figure>

<p>在使用基于名义类型的语言，比如C#或Java中，这段代码会报错，因为Person类没有明确说明其实现了Named接口。</p>
<p>TypeScript的结构性子类型是根据JavaScript代码的典型写法来设计的。 因为JavaScript里广泛地使用匿名对象，例如函数表达式和对象字面量，所以使用结构类型系统来描述这些类型比使用名义类型系统更好。</p>
<h4 id="可靠性"><a href="#可靠性" class="headerlink" title="可靠性"></a>可靠性</h4><p>TypeScript的类型系统允许某些在编译阶段无法确认其安全性的操作。当一个类型系统具此属性时，被当做是“不可靠”的。TypeScript允许这种不可靠行为的发生是经过仔细考虑的。通过这篇文章，我们会解释什么时候会发生这种情况和其有利的一面。</p>
<p>TypeScript结构化类型系统的基本规则是，如果<code>x</code>要兼容<code>y</code>，那么<code>y</code>至少具有与<code>x</code>相同的属性。比如：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Named &#123;</span><br><span class="line">    name: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> x: Named;</span><br><span class="line"><span class="comment">// y's inferred type is &#123; name: string; location: string; &#125;</span></span><br><span class="line"><span class="keyword">let</span> y = &#123; name: <span class="string">'Alice'</span>, location: <span class="string">'Seattle'</span> &#125;;</span><br><span class="line">x = y;</span><br></pre></td></tr></table></figure>

<p>这里要检查<code>y</code>是否能赋值给<code>x</code>，编译器检查<code>x</code>中的每个属性，看是否能在<code>y</code>中也找到对应属性。 在这个例子中，<code>y</code>必须包含名字是<code>name</code>的<code>string</code>类型成员。<code>y</code>满足条件，因此赋值正确。</p>
<p>检查函数参数时使用相同的规则：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">greet</span>(<span class="params">n: Named</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'Hello, '</span> + n.name);</span><br><span class="line">&#125;</span><br><span class="line">greet(y); <span class="comment">// OK</span></span><br></pre></td></tr></table></figure>

<p>注意，<code>y</code>有个额外的<code>location</code>属性，但这不会引发错误。 只有目标类型（这里是<code>Named</code>）的成员会被一一检查是否兼容。</p>
<p>这个比较过程是递归进行的，检查每个成员及子成员。</p>
<h4 id="比较两个函数"><a href="#比较两个函数" class="headerlink" title="比较两个函数"></a>比较两个函数</h4><p>相对来讲，在比较原始类型和对象类型的时候是比较容易理解的，问题是如何判断两个函数是兼容的。 下面我们从两个简单的函数入手，它们仅是参数列表略有不同：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = <span class="function">(<span class="params">a: <span class="built_in">number</span></span>) =&gt;</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">let</span> y = <span class="function">(<span class="params">b: <span class="built_in">number</span>, s: <span class="built_in">string</span></span>) =&gt;</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">y = x; <span class="comment">// OK</span></span><br><span class="line">x = y; <span class="comment">// Error</span></span><br></pre></td></tr></table></figure>

<p><strong>要查看<code>x</code>是否能赋值给<code>y</code>，首先看它们的参数列表。 <code>x</code>的每个参数必须能在<code>y</code>里找到对应类型的参数。 注意的是参数的名字相同与否无所谓，只看它们的类型。 这里，<code>x</code>的每个参数在<code>y</code>中都能找到对应的参数，所以允许赋值。</strong></p>
<p>第二个赋值错误，因为<code>y</code>有个必需的第二个参数，但是<code>x</code>并没有，所以不允许赋值。</p>
<p>你可能会疑惑为什么允许<code>忽略</code>参数，像例子<code>y = x</code>中那样。 原因是忽略额外的参数在JavaScript里是很常见的。 例如，<code>Array#forEach</code>给回调函数传3个参数：数组元素，索引和整个数组。 尽管如此，传入一个只使用第一个参数的回调函数也是很有用的：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> items = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// Don't force these extra arguments</span></span><br><span class="line">items.forEach(<span class="function">(<span class="params">item, index, array</span>) =&gt;</span> <span class="built_in">console</span>.log(item));</span><br><span class="line"></span><br><span class="line"><span class="comment">// Should be OK!</span></span><br><span class="line">items.forEach(<span class="function">(<span class="params">item</span>) =&gt;</span> <span class="built_in">console</span>.log(item));</span><br></pre></td></tr></table></figure>

<p>下面来看看如何处理返回值类型，创建两个仅是返回值类型不同的函数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> x = <span class="function"><span class="params">()</span> =&gt;</span> (&#123;name: <span class="string">'Alice'</span>&#125;);</span><br><span class="line"><span class="keyword">let</span> y = <span class="function"><span class="params">()</span> =&gt;</span> (&#123;name: <span class="string">'Alice'</span>, location: <span class="string">'Seattle'</span>&#125;);</span><br><span class="line"></span><br><span class="line">x = y; <span class="comment">// OK</span></span><br><span class="line">y = x; <span class="comment">// Error, because x() lacks a location property</span></span><br></pre></td></tr></table></figure>

<p>类型系统强制源函数的返回值类型必须是目标函数返回值类型的子类型。</p>
<p>函数参数双向协变：</p>
<p>当比较函数参数类型时，只有当源函数参数能够赋值给目标函数或者反过来时才能赋值成功。 这是不稳定的，因为调用者可能传入了一个具有更精确类型信息的函数，但是调用这个传入的函数的时候却使用了不是那么精确的类型信息。 实际上，这极少会发生错误，并且能够实现很多JavaScript里的常见模式。例如：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> EventType &#123; Mouse, Keyboard &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Event &#123; timestamp: <span class="built_in">number</span>; &#125;</span><br><span class="line"><span class="keyword">interface</span> MouseEvent <span class="keyword">extends</span> Event &#123; x: <span class="built_in">number</span>; y: <span class="built_in">number</span> &#125;</span><br><span class="line"><span class="keyword">interface</span> KeyEvent <span class="keyword">extends</span> Event &#123; keyCode: <span class="built_in">number</span> &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">listenEvent</span>(<span class="params">eventType: EventType, handler: (n: Event) =&gt; <span class="built_in">void</span></span>) </span>&#123;</span><br><span class="line">    <span class="comment">/* ... */</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Unsound, but useful and common</span></span><br><span class="line">listenEvent(EventType.Mouse, <span class="function">(<span class="params">e: MouseEvent</span>) =&gt;</span> <span class="built_in">console</span>.log(e.x + <span class="string">','</span> + e.y));</span><br><span class="line"></span><br><span class="line"><span class="comment">// Undesirable alternatives in presence of soundness</span></span><br><span class="line">listenEvent(EventType.Mouse, <span class="function">(<span class="params">e: Event</span>) =&gt;</span> <span class="built_in">console</span>.log((&lt;MouseEvent&gt;e).x + <span class="string">','</span> + (&lt;MouseEvent&gt;e).y));</span><br><span class="line">listenEvent(EventType.Mouse, &lt;<span class="function">(<span class="params">e: Event</span>) =&gt;</span> <span class="built_in">void</span>&gt;<span class="function">(<span class="params">(<span class="params">e: MouseEvent</span>) =&gt; <span class="built_in">console</span>.log(<span class="params">e.x + ',' + e.y</span>)</span>));</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function">// <span class="params">Still</span> <span class="params">disallowed</span> (<span class="params">clear error</span>). <span class="params">Type</span> <span class="params">safety</span> <span class="params">enforced</span> <span class="params">for</span> <span class="params">wholly</span> <span class="params">incompatible</span> <span class="params">types</span></span></span><br><span class="line"><span class="function"><span class="params">listenEvent</span>(<span class="params">EventType.Mouse, (<span class="params">e: <span class="built_in">number</span></span>) =&gt; <span class="built_in">console</span>.log(<span class="params">e</span>)</span>);</span></span><br></pre></td></tr></table></figure>

<p>可选参数及剩余参数：</p>
<p>比较函数兼容性的时候，可选参数与必须参数是可互换的。 源类型上有额外的可选参数不是错误，目标类型的可选参数在源类型里没有对应的参数也不是错误。</p>
<p>当一个函数有剩余参数时，它被当做无限个可选参数。</p>
<p>这对于类型系统来说是不稳定的，但从运行时的角度来看，可选参数一般来说是不强制的，因为对于大多数函数来说相当于传递了一些<code>undefinded</code>。</p>
<p>有一个好的例子，常见的函数接收一个回调函数并用对于程序员来说是可预知的参数但对类型系统来说是不确定的参数来调用：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">invokeLater</span>(<span class="params">args: <span class="built_in">any</span>[], callback: (...args: <span class="built_in">any</span>[]) =&gt; <span class="built_in">void</span></span>) </span>&#123;</span><br><span class="line">    <span class="comment">/* ... Invoke callback with 'args' ... */</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Unsound - invokeLater "might" provide any number of arguments</span></span><br><span class="line">invokeLater([<span class="number">1</span>, <span class="number">2</span>], <span class="function">(<span class="params">x, y</span>) =&gt;</span> <span class="built_in">console</span>.log(x + <span class="string">', '</span> + y));</span><br><span class="line"></span><br><span class="line"><span class="comment">// Confusing (x and y are actually required) and undiscoverable</span></span><br><span class="line">invokeLater([<span class="number">1</span>, <span class="number">2</span>], <span class="function">(<span class="params">x?, y?</span>) =&gt;</span> <span class="built_in">console</span>.log(x + <span class="string">', '</span> + y));</span><br></pre></td></tr></table></figure>

<p>函数重载：</p>
<p>对于有重载的函数，源函数的每个重载都要在目标函数上找到对应的函数签名。 这确保了目标函数可以在所有源函数可调用的地方调用。</p>
<h4 id="枚举-2"><a href="#枚举-2" class="headerlink" title="枚举"></a>枚举</h4><p>枚举类型与数字类型兼容，并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。比如，</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> Status &#123; Ready, Waiting &#125;;</span><br><span class="line"><span class="keyword">enum</span> Color &#123; Red, Blue, Green &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> status = Status.Ready;</span><br><span class="line">status = Color.Green;  <span class="comment">// Error</span></span><br></pre></td></tr></table></figure>

<h4 id="类-1"><a href="#类-1" class="headerlink" title="类"></a>类</h4><p>类与对象字面量和接口差不多，但有一点不同：类有静态部分和实例部分的类型。 比较两个类类型的对象时，只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Animal &#123;</span><br><span class="line">    feet: <span class="built_in">number</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">name: <span class="built_in">string</span>, numFeet: <span class="built_in">number</span></span>) &#123; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Size &#123;</span><br><span class="line">    feet: <span class="built_in">number</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">numFeet: <span class="built_in">number</span></span>) &#123; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a: Animal;</span><br><span class="line"><span class="keyword">let</span> s: Size;</span><br><span class="line"></span><br><span class="line">a = s;  <span class="comment">// OK</span></span><br><span class="line">s = a;  <span class="comment">// OK</span></span><br></pre></td></tr></table></figure>

<p>类的私有成员和受保护成员：</p>
<p>类的私有成员和受保护成员会影响兼容性。 当检查类实例的兼容时，如果目标类型包含一个私有成员，那么源类型必须包含来自同一个类的这个私有成员。 同样地，这条规则也适用于包含受保护成员实例的类型检查。 这允许子类赋值给父类，但是不能赋值给其它有同样类型的类。</p>
<h3 id="泛型-1"><a href="#泛型-1" class="headerlink" title="泛型"></a>泛型</h3><p>因为TypeScript是结构性的类型系统，类型参数只影响使用其做为类型一部分的结果类型。比如，</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Empty&lt;T&gt; &#123;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> x: Empty&lt;<span class="built_in">number</span>&gt;;</span><br><span class="line"><span class="keyword">let</span> y: Empty&lt;<span class="built_in">string</span>&gt;;</span><br><span class="line"></span><br><span class="line">x = y;  <span class="comment">// OK, because y matches structure of x</span></span><br></pre></td></tr></table></figure>

<p>上面代码里，<code>x</code>和<code>y</code>是兼容的，因为它们的结构使用类型参数时并没有什么不同。 把这个例子改变一下，增加一个成员，就能看出是如何工作的了：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> NotEmpty&lt;T&gt; &#123;</span><br><span class="line">    data: T;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> x: NotEmpty&lt;<span class="built_in">number</span>&gt;;</span><br><span class="line"><span class="keyword">let</span> y: NotEmpty&lt;<span class="built_in">string</span>&gt;;</span><br><span class="line"></span><br><span class="line">x = y;  <span class="comment">// Error, because x and y are not compatible</span></span><br></pre></td></tr></table></figure>

<p>在这里，泛型类型在使用时就好比不是一个泛型类型。</p>
<p>对于没指定泛型类型的泛型参数时，会把所有泛型参数当成<code>any</code>比较。 然后用结果类型进行比较，就像上面第一个例子。</p>
<p>比如，</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> identity = <span class="function"><span class="keyword">function</span>&lt;<span class="title">T</span>&gt;(<span class="params">x: T</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> reverse = <span class="function"><span class="keyword">function</span>&lt;<span class="title">U</span>&gt;(<span class="params">y: U</span>): <span class="title">U</span> </span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">identity = reverse;  <span class="comment">// OK, because (x: any) =&gt; any matches (y: any) =&gt; any</span></span><br></pre></td></tr></table></figure>

<h3 id="高级主题"><a href="#高级主题" class="headerlink" title="高级主题"></a>高级主题</h3><p>子类型与赋值：</p>
<p>目前为止，我们使用了“兼容性”，它在语言规范里没有定义。 在TypeScript里，有两种兼容性：子类型和赋值。 它们的不同点在于，赋值扩展了子类型兼容性，增加了一些规则，允许和<code>any</code>来回赋值，以及<code>enum</code>和对应数字值之间的来回赋值。</p>
<p>语言里的不同地方分别使用了它们之中的机制。 实际上，类型兼容性是由赋值兼容性来控制的，即使在<code>implements</code>和<code>extends</code>语句也不例外。</p>
<h2 id="高级类型"><a href="#高级类型" class="headerlink" title="高级类型"></a>高级类型</h2><h3 id="交叉类型"><a href="#交叉类型" class="headerlink" title="交叉类型"></a>交叉类型</h3><p>交叉类型是将多个类型合并为一个类型。 这让我们<strong>可以把现有的多种类型叠加到一起成为一种类型，它包含了所需的所有类型的特性。</strong> 例如， <code>Person &amp; Serializable &amp; Loggable</code>同时是 <code>Person</code> <em>和</em> <code>Serializable</code> <em>和</em> <code>Loggable</code>。 就是说这个类型的对象同时拥有了这三种类型的成员。</p>
<p>我们大多是在混入（mixins）或其它不适合典型面向对象模型的地方看到交叉类型的使用。 （在JavaScript里发生这种情况的场合很多！） 下面是如何创建混入的一个简单例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">extend</span>&lt;<span class="title">T</span>, <span class="title">U</span>&gt;(<span class="params">first: T, second: U</span>): <span class="title">T</span> &amp; <span class="title">U</span> </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> result = &lt;T &amp; U&gt;&#123;&#125;;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> id <span class="keyword">in</span> first) &#123;</span><br><span class="line">        (&lt;<span class="built_in">any</span>&gt;result)[id] = (&lt;<span class="built_in">any</span>&gt;first)[id];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> id <span class="keyword">in</span> second) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!result.hasOwnProperty(id)) &#123;</span><br><span class="line">            (&lt;<span class="built_in">any</span>&gt;result)[id] = (&lt;<span class="built_in">any</span>&gt;second)[id];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Person &#123;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params"><span class="keyword">public</span> name: <span class="built_in">string</span></span>) &#123; &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> Loggable &#123;</span><br><span class="line">    log(): <span class="built_in">void</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> ConsoleLogger <span class="keyword">implements</span> Loggable &#123;</span><br><span class="line">    log() &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> jim = extend(<span class="keyword">new</span> Person(<span class="string">"Jim"</span>), <span class="keyword">new</span> ConsoleLogger());</span><br><span class="line"><span class="keyword">var</span> n = jim.name;</span><br><span class="line">jim.log();</span><br></pre></td></tr></table></figure>

<h3 id="联合类型"><a href="#联合类型" class="headerlink" title="联合类型"></a>联合类型</h3><p>联合类型与交叉类型很有关联，但是使用上却完全不同。 偶尔你会遇到这种情况，一个代码库希望传入 <code>number</code>或 <code>string</code>类型的参数。 例如下面的函数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Takes a string and adds "padding" to the left.</span></span><br><span class="line"><span class="comment"> * If 'padding' is a string, then 'padding' is appended to the left side.</span></span><br><span class="line"><span class="comment"> * If 'padding' is a number, then that number of spaces is added to the left side.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">padLeft</span>(<span class="params">value: <span class="built_in">string</span>, padding: <span class="built_in">any</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> padding === <span class="string">"number"</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">Array</span>(padding + <span class="number">1</span>).join(<span class="string">" "</span>) + value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> padding === <span class="string">"string"</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> padding + value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">`Expected string or number, got '<span class="subst">$&#123;padding&#125;</span>'.`</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">padLeft(<span class="string">"Hello world"</span>, <span class="number">4</span>); <span class="comment">// returns "    Hello world"</span></span><br></pre></td></tr></table></figure>

<p><code>padLeft</code>存在一个问题， <code>padding</code>参数的类型指定成了 <code>any</code>。 这就是说我们可以传入一个既不是 <code>number</code>也不是 <code>string</code>类型的参数，但是TypeScript却不报错。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> indentedString = padLeft(<span class="string">"Hello world"</span>, <span class="literal">true</span>); <span class="comment">// 编译阶段通过，运行时报错</span></span><br></pre></td></tr></table></figure>

<p>在传统的面向对象语言里，我们可能会将这两种类型抽象成有层级的类型。 这么做显然是非常清晰的，但同时也存在了过度设计。 <code>padLeft</code>原始版本的好处之一是允许我们传入原始类型。 这样做的话使用起来既简单又方便。 如果我们就是想使用已经存在的函数的话，这种新的方式就不适用了。</p>
<p>代替 <code>any</code>， 我们可以使用 <em>联合类型</em>做为 <code>padding</code>的参数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Takes a string and adds "padding" to the left.</span></span><br><span class="line"><span class="comment"> * If 'padding' is a string, then 'padding' is appended to the left side.</span></span><br><span class="line"><span class="comment"> * If 'padding' is a number, then that number of spaces is added to the left side.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">padLeft</span>(<span class="params">value: <span class="built_in">string</span>, padding: <span class="built_in">string</span> | <span class="built_in">number</span></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> indentedString = padLeft(<span class="string">"Hello world"</span>, <span class="literal">true</span>); <span class="comment">// errors during compilation</span></span><br></pre></td></tr></table></figure>

<p>联合类型表示一个值可以是几种类型之一。 我们用竖线（ <code>|</code>）分隔每个类型，所以 <code>number | string | boolean</code>表示一个值可以是 <code>number</code>， <code>string</code>，或 <code>boolean</code>。</p>
<p>如果一个值是联合类型，我们只能访问此联合类型的所有类型里共有的成员。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Bird &#123;</span><br><span class="line">    fly();</span><br><span class="line">    layEggs();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Fish &#123;</span><br><span class="line">    swim();</span><br><span class="line">    layEggs();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getSmallPet</span>(<span class="params"></span>): <span class="title">Fish</span> | <span class="title">Bird</span> </span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> pet = getSmallPet();</span><br><span class="line">pet.layEggs(); <span class="comment">// okay</span></span><br><span class="line">pet.swim();    <span class="comment">// errors</span></span><br></pre></td></tr></table></figure>

<p>这里的联合类型可能有点复杂，但是你很容易就习惯了。 如果一个值的类型是 <code>A | B</code>，我们能够 <em>确定</em>的是它包含了 <code>A</code> <em>和</em> <code>B</code>中共有的成员。 这个例子里， <code>Bird</code>具有一个 <code>fly</code>成员。 我们不能确定一个 <code>Bird | Fish</code>类型的变量是否有 <code>fly</code>方法。 如果变量在运行时是 <code>Fish</code>类型，那么调用 <code>pet.fly()</code>就出错了。</p>
<h3 id="类型保护与区分"><a href="#类型保护与区分" class="headerlink" title="类型保护与区分"></a>类型保护与区分</h3><p>联合类型适合于那些值可以为不同类型的情况。 但当我们想确切地了解是否为 <code>Fish</code>时怎么办？ JavaScript里常用来区分2个可能值的方法是检查成员是否存在。 如之前提及的，我们只能访问联合类型中共同拥有的成员。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> pet = getSmallPet();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 每一个成员访问都会报错</span></span><br><span class="line"><span class="keyword">if</span> (pet.swim) &#123;</span><br><span class="line">    pet.swim();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (pet.fly) &#123;</span><br><span class="line">    pet.fly();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>为了让这段代码工作，我们要使用类型断言：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> pet = getSmallPet();</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> ((&lt;Fish&gt;pet).swim) &#123;</span><br><span class="line">    (&lt;Fish&gt;pet).swim();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">    (&lt;Bird&gt;pet).fly();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>用户自定义的类型保护：</p>
<p>这里可以注意到我们不得不多次使用类型断言。 假若我们一旦检查过类型，就能在之后的每个分支里清楚地知道 <code>pet</code>的类型的话就好了。</p>
<p>TypeScript里的 <em>类型保护</em>机制让它成为了现实。 类型保护就是一些表达式，它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护，我们只要简单地定义一个函数，它的返回值是一个 <em>类型谓词</em>：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">isFish</span>(<span class="params">pet: Fish | Bird</span>): <span class="title">pet</span> <span class="title">is</span> <span class="title">Fish</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> (&lt;Fish&gt;pet).swim !== <span class="literal">undefined</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在这个例子里， <code>pet is Fish</code>就是类型谓词。 谓词为 <code>parameterName is Type</code>这种形式， <code>parameterName</code>必须是来自于当前函数签名里的一个参数名。</p>
<p>每当使用一些变量调用 <code>isFish</code>时，TypeScript会将变量缩减为那个具体的类型，只要这个类型与变量的原始类型是兼容的。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 'swim' 和 'fly' 调用都没有问题了</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (isFish(pet)) &#123;</span><br><span class="line">    pet.swim();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">else</span> &#123;</span><br><span class="line">    pet.fly();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>注意TypeScript不仅知道在 <code>if</code>分支里 <code>pet</code>是 <code>Fish</code>类型； 它还清楚在 <code>else</code>分支里，一定 <em>不是</em> <code>Fish</code>类型，一定是 <code>Bird</code>类型。</p>
<p><code>typeof</code>类型保护：</p>
<p>现在我们回过头来看看怎么使用联合类型书写 <code>padLeft</code>代码。 我们可以像下面这样利用类型断言来写：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">isNumber</span>(<span class="params">x: <span class="built_in">any</span></span>): <span class="title">x</span> <span class="title">is</span> <span class="title">number</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">typeof</span> x === <span class="string">"number"</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">isString</span>(<span class="params">x: <span class="built_in">any</span></span>): <span class="title">x</span> <span class="title">is</span> <span class="title">string</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">typeof</span> x === <span class="string">"string"</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">padLeft</span>(<span class="params">value: <span class="built_in">string</span>, padding: <span class="built_in">string</span> | <span class="built_in">number</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (isNumber(padding)) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">Array</span>(padding + <span class="number">1</span>).join(<span class="string">" "</span>) + value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (isString(padding)) &#123;</span><br><span class="line">        <span class="keyword">return</span> padding + value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">`Expected string or number, got '<span class="subst">$&#123;padding&#125;</span>'.`</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然而，必须要定义一个函数来判断类型是否是原始类型，这太痛苦了。 幸运的是，现在我们不必将 <code>typeof x === &quot;number&quot;</code>抽象成一个函数，因为TypeScript可以将它识别为一个类型保护。 也就是说我们可以直接在代码里检查类型了。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">padLeft</span>(<span class="params">value: <span class="built_in">string</span>, padding: <span class="built_in">string</span> | <span class="built_in">number</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> padding === <span class="string">"number"</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">Array</span>(padding + <span class="number">1</span>).join(<span class="string">" "</span>) + value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> padding === <span class="string">"string"</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> padding + value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">`Expected string or number, got '<span class="subst">$&#123;padding&#125;</span>'.`</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这些* <code>typeof</code>类型保护*只有两种形式能被识别： <code>typeof v === &quot;typename&quot;</code>和 <code>typeof v !== &quot;typename&quot;</code>， <code>&quot;typename&quot;</code>必须是 <code>&quot;number&quot;</code>， <code>&quot;string&quot;</code>， <code>&quot;boolean&quot;</code>或 <code>&quot;symbol&quot;</code>。 但是TypeScript并不会阻止你与其它字符串比较，语言不会把那些表达式识别为类型保护。</p>
<p><code>instanceof</code>类型保护：</p>
<p>如果你已经阅读了 <code>typeof</code>类型保护并且对JavaScript里的 <code>instanceof</code>操作符熟悉的话，你可能已经猜到了这节要讲的内容。</p>
<p><em><code>instanceof</code>类型保护</em>是通过构造函数来细化类型的一种方式。 比如，我们借鉴一下之前字符串填充的例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Padder &#123;</span><br><span class="line">    getPaddingString(): <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> SpaceRepeatingPadder <span class="keyword">implements</span> Padder &#123;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> numSpaces: <span class="built_in">number</span></span>) &#123; &#125;</span><br><span class="line">    getPaddingString() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">Array</span>(<span class="keyword">this</span>.numSpaces + <span class="number">1</span>).join(<span class="string">" "</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> StringPadder <span class="keyword">implements</span> Padder &#123;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params"><span class="keyword">private</span> value: <span class="built_in">string</span></span>) &#123; &#125;</span><br><span class="line">    getPaddingString() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.value;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getRandomPadder</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Math</span>.random() &lt; <span class="number">0.5</span> ?</span><br><span class="line">        <span class="keyword">new</span> SpaceRepeatingPadder(<span class="number">4</span>) :</span><br><span class="line">        <span class="keyword">new</span> StringPadder(<span class="string">"  "</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 类型为SpaceRepeatingPadder | StringPadder</span></span><br><span class="line"><span class="keyword">let</span> padder: Padder = getRandomPadder();</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (padder <span class="keyword">instanceof</span> SpaceRepeatingPadder) &#123;</span><br><span class="line">    padder; <span class="comment">// 类型细化为'SpaceRepeatingPadder'</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (padder <span class="keyword">instanceof</span> StringPadder) &#123;</span><br><span class="line">    padder; <span class="comment">// 类型细化为'StringPadder'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>instanceof</code>的右侧要求是一个构造函数，TypeScript将细化为：</p>
<ol>
<li>此构造函数的 <code>prototype</code>属性的类型，如果它的类型不为 <code>any</code>的话</li>
<li>构造签名所返回的类型的联合</li>
</ol>
<p>以此顺序。</p>
<h3 id="可以为null的类型"><a href="#可以为null的类型" class="headerlink" title="可以为null的类型"></a>可以为null的类型</h3><p>TypeScript具有两种特殊的类型， <code>null</code>和 <code>undefined</code>，它们分别具有值null和undefined. 我们在[基础类型](./Basic Types.md)一节里已经做过简要说明。 默认情况下，类型检查器认为 <code>null</code>与 <code>undefined</code>可以赋值给任何类型。 <code>null</code>与 <code>undefined</code>是所有其它类型的一个有效值。 这也意味着，你阻止不了将它们赋值给其它类型，就算是你想要阻止这种情况也不行。 <code>null</code>的发明者，Tony Hoare，称它为 <a href="https://en.wikipedia.org/wiki/Null_pointer#History" target="_blank" rel="noopener">价值亿万美金的错误</a>。</p>
<p><code>--strictNullChecks</code>标记可以解决此错误：当你声明一个变量时，它不会自动地包含 <code>null</code>或 <code>undefined</code>。 你可以使用联合类型明确的包含它们：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> s = <span class="string">"foo"</span>;</span><br><span class="line">s = <span class="literal">null</span>; <span class="comment">// 错误, 'null'不能赋值给'string'</span></span><br><span class="line"><span class="keyword">let</span> sn: <span class="built_in">string</span> | <span class="literal">null</span> = <span class="string">"bar"</span>;</span><br><span class="line">sn = <span class="literal">null</span>; <span class="comment">// 可以</span></span><br><span class="line"></span><br><span class="line">sn = <span class="literal">undefined</span>; <span class="comment">// error, 'undefined'不能赋值给'string | null'</span></span><br></pre></td></tr></table></figure>

<p>注意，按照JavaScript的语义，TypeScript会把 <code>null</code>和 <code>undefined</code>区别对待。 <code>string | null</code>， <code>string | undefined</code>和 <code>string | undefined | null</code>是不同的类型。</p>
<p>可选参数和可选属性：</p>
<p>使用了 <code>--strictNullChecks</code>，可选参数会被自动地加上 <code>| undefined</code>:</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params">x: <span class="built_in">number</span>, y?: <span class="built_in">number</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> x + (y || <span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line">f(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line">f(<span class="number">1</span>);</span><br><span class="line">f(<span class="number">1</span>, <span class="literal">undefined</span>);</span><br><span class="line">f(<span class="number">1</span>, <span class="literal">null</span>); <span class="comment">// error, 'null' is not assignable to 'number | undefined'</span></span><br></pre></td></tr></table></figure>

<p>可选属性也会有同样的处理：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> C &#123;</span><br><span class="line">    a: <span class="built_in">number</span>;</span><br><span class="line">    b?: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> c = <span class="keyword">new</span> C();</span><br><span class="line">c.a = <span class="number">12</span>;</span><br><span class="line">c.a = <span class="literal">undefined</span>; <span class="comment">// error, 'undefined' is not assignable to 'number'</span></span><br><span class="line">c.b = <span class="number">13</span>;</span><br><span class="line">c.b = <span class="literal">undefined</span>; <span class="comment">// ok</span></span><br><span class="line">c.b = <span class="literal">null</span>; <span class="comment">// error, 'null' is not assignable to 'number | undefined'</span></span><br></pre></td></tr></table></figure>

<p>类型保护和类型断言：</p>
<p>由于可以为null的类型是通过联合类型实现，那么你需要使用类型保护来去除 <code>null</code>。 幸运地是这与在JavaScript里写的代码一致：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params">sn: <span class="built_in">string</span> | <span class="literal">null</span></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (sn == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"default"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sn;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里很明显地去除了 <code>null</code>，你也可以使用短路运算符：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params">sn: <span class="built_in">string</span> | <span class="literal">null</span></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> sn || <span class="string">"default"</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果编译器不能够去除 <code>null</code>或 <code>undefined</code>，你可以使用类型断言手动去除。 语法是添加 <code>!</code>后缀： <code>identifier!</code>从 <code>identifier</code>的类型里去除了 <code>null</code>和 <code>undefined</code>：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">broken</span>(<span class="params">name: <span class="built_in">string</span> | <span class="literal">null</span></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">postfix</span>(<span class="params">epithet: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> name.charAt(<span class="number">0</span>) + <span class="string">'.  the '</span> + epithet; <span class="comment">// error, 'name' is possibly null</span></span><br><span class="line">  &#125;</span><br><span class="line">  name = name || <span class="string">"Bob"</span>;</span><br><span class="line">  <span class="keyword">return</span> postfix(<span class="string">"great"</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fixed</span>(<span class="params">name: <span class="built_in">string</span> | <span class="literal">null</span></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">postfix</span>(<span class="params">epithet: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> name!.charAt(<span class="number">0</span>) + <span class="string">'.  the '</span> + epithet; <span class="comment">// ok</span></span><br><span class="line">  &#125;</span><br><span class="line">  name = name || <span class="string">"Bob"</span>;</span><br><span class="line">  <span class="keyword">return</span> postfix(<span class="string">"great"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>本例使用了嵌套函数，因为编译器无法去除嵌套函数的null（除非是立即调用的函数表达式）。 因为它无法跟踪所有对嵌套函数的调用，尤其是你将内层函数做为外层函数的返回值。 如果无法知道函数在哪里被调用，就无法知道调用时 <code>name</code>的类型。</p>
<h3 id="类型别名"><a href="#类型别名" class="headerlink" title="类型别名"></a>类型别名</h3><p>类型别名会给一个类型起个新名字。 类型别名有时和接口很像，但是可以作用于原始值，联合类型，元组以及其它任何你需要手写的类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Name = <span class="built_in">string</span>;</span><br><span class="line"><span class="keyword">type</span> NameResolver = <span class="function"><span class="params">()</span> =&gt;</span> <span class="built_in">string</span>;</span><br><span class="line"><span class="keyword">type</span> NameOrResolver = Name | NameResolver;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getName</span>(<span class="params">n: NameOrResolver</span>): <span class="title">Name</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> n === <span class="string">'string'</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> n;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> n();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>起别名不会新建一个类型 - 它创建了一个新 <em>名字</em>来引用那个类型。 给原始类型起别名通常没什么用，尽管可以做为文档的一种形式使用。</p>
<p>同接口一样，类型别名也可以是泛型 - 我们可以添加类型参数并且在别名声明的右侧传入：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Container&lt;T&gt; = &#123; value: T &#125;;</span><br></pre></td></tr></table></figure>

<p>我们也可以使用类型别名来在属性里引用自己：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Tree&lt;T&gt; = &#123;</span><br><span class="line">    value: T;</span><br><span class="line">    left: Tree&lt;T&gt;;</span><br><span class="line">    right: Tree&lt;T&gt;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>与交叉类型一起使用，我们可以创建出一些十分稀奇古怪的类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> LinkedList&lt;T&gt; = T &amp; &#123; next: LinkedList&lt;T&gt; &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Person &#123;</span><br><span class="line">    name: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> people: LinkedList&lt;Person&gt;;</span><br><span class="line"><span class="keyword">var</span> s = people.name;</span><br><span class="line"><span class="keyword">var</span> s = people.next.name;</span><br><span class="line"><span class="keyword">var</span> s = people.next.next.name;</span><br><span class="line"><span class="keyword">var</span> s = people.next.next.next.name;</span><br></pre></td></tr></table></figure>

<p>然而，类型别名不能出现在声明右侧的任何地方。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Yikes = <span class="built_in">Array</span>&lt;Yikes&gt;; <span class="comment">// error</span></span><br></pre></td></tr></table></figure>

<p>接口 vs. 类型别名：</p>
<p>像我们提到的，类型别名可以像接口一样；然而，仍有一些细微差别。</p>
<p>其一，接口创建了一个新的名字，可以在其它任何地方使用。 类型别名并不创建新名字—比如，错误信息就不会使用别名。 在下面的示例代码里，在编译器中将鼠标悬停在 <code>interfaced</code>上，显示它返回的是 <code>Interface</code>，但悬停在 <code>aliased</code>上时，显示的却是对象字面量类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Alias = &#123; num: <span class="built_in">number</span> &#125;</span><br><span class="line"><span class="keyword">interface</span> Interface &#123;</span><br><span class="line">    num: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">declare</span> <span class="function"><span class="keyword">function</span> <span class="title">aliased</span>(<span class="params">arg: Alias</span>): <span class="title">Alias</span></span>;</span><br><span class="line"><span class="keyword">declare</span> <span class="function"><span class="keyword">function</span> <span class="title">interfaced</span>(<span class="params">arg: Interface</span>): <span class="title">Interface</span></span>;</span><br></pre></td></tr></table></figure>

<p>另一个重要区别是类型别名不能被 <code>extends</code>和 <code>implements</code>（自己也不能 <code>extends</code>和 <code>implements</code>其它类型）。 因为 <a href="https://en.wikipedia.org/wiki/Open/closed_principle" target="_blank" rel="noopener">软件中的对象应该对于扩展是开放的，但是对于修改是封闭的</a>，你应该尽量去使用接口代替类型别名。</p>
<p>另一方面，如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型，这时通常会使用类型别名。</p>
<p>字符串字面量类型：</p>
<p>字符串字面量类型允许你指定字符串必须的固定值。 在实际应用中，字符串字面量类型可以与联合类型，类型保护和类型别名很好的配合。 通过结合使用这些特性，你可以实现类似枚举类型的字符串。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Easing = <span class="string">"ease-in"</span> | <span class="string">"ease-out"</span> | <span class="string">"ease-in-out"</span>;</span><br><span class="line"><span class="keyword">class</span> UIElement &#123;</span><br><span class="line">    animate(dx: <span class="built_in">number</span>, dy: <span class="built_in">number</span>, easing: Easing) &#123;</span><br><span class="line">        <span class="keyword">if</span> (easing === <span class="string">"ease-in"</span>) &#123;</span><br><span class="line">            <span class="comment">// ...</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (easing === <span class="string">"ease-out"</span>) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (easing === <span class="string">"ease-in-out"</span>) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// error! should not pass null or undefined.</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> button = <span class="keyword">new</span> UIElement();</span><br><span class="line">button.animate(<span class="number">0</span>, <span class="number">0</span>, <span class="string">"ease-in"</span>);</span><br><span class="line">button.animate(<span class="number">0</span>, <span class="number">0</span>, <span class="string">"uneasy"</span>); <span class="comment">// error: "uneasy" is not allowed here</span></span><br></pre></td></tr></table></figure>

<p>你只能从三种允许的字符中选择其一来做为参数传递，传入其它值则会产生错误。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Argument of type &#39;&quot;uneasy&quot;&#39; is not assignable to parameter of type &#39;&quot;ease-in&quot; | &quot;ease-out&quot; | &quot;ease-in-out&quot;&#39;</span><br></pre></td></tr></table></figure>

<p>字符串字面量类型还可以用于区分函数重载：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createElement</span>(<span class="params">tagName: "img"</span>): <span class="title">HTMLImageElement</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createElement</span>(<span class="params">tagName: "input"</span>): <span class="title">HTMLInputElement</span></span>;</span><br><span class="line"><span class="comment">// ... more overloads ...</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createElement</span>(<span class="params">tagName: <span class="built_in">string</span></span>): <span class="title">Element</span> </span>&#123;</span><br><span class="line">    <span class="comment">// ... code goes here ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>数字字面量类型：</p>
<p>TypeScript还具有数字字面量类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">rollDie</span>(<span class="params"></span>): 1 | 2 | 3 | 4 | 5 | 6 </span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们很少直接这样使用，但它们可以用在缩小范围调试bug的时候：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params">x: <span class="built_in">number</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (x !== <span class="number">1</span> || x !== <span class="number">2</span>) &#123;</span><br><span class="line">        <span class="comment">//         ~~~~~~~</span></span><br><span class="line">        <span class="comment">// Operator '!==' cannot be applied to types '1' and '2'.</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>换句话说，当 <code>x</code>与 <code>2</code>进行比较的时候，它的值必须为 <code>1</code>，这就意味着上面的比较检查是非法的。</p>
<p>枚举成员类型：</p>
<p>如我们在 <a href="https://www.tslang.cn/docs/handbook/Enums.md#union-enums-and-enum-member-types" target="_blank" rel="noopener">枚举</a>一节里提到的，当每个枚举成员都是用字面量初始化的时候枚举成员是具有类型的。</p>
<p>在我们谈及“单例类型”的时候，多数是指枚举成员类型和数字/字符串字面量类型，尽管大多数用户会互换使用“单例类型”和“字面量类型”。</p>
<h3 id="可辨识联合"><a href="#可辨识联合" class="headerlink" title="可辨识联合"></a>可辨识联合</h3><p>你可以合并单例类型，联合类型，类型保护和类型别名来创建一个叫做 <em>可辨识联合</em>的高级模式，它也称做 <em>标签联合</em>或 <em>代数数据类型</em>。 可辨识联合在函数式编程很有用处。 一些语言会自动地为你辨识联合；而TypeScript则基于已有的JavaScript模式。 它具有3个要素：</p>
<ol>
<li>具有普通的单例类型属性— <em>可辨识的特征</em>。</li>
<li>一个类型别名包含了那些类型的联合— <em>联合</em>。</li>
<li>此属性上的类型保护。</li>
</ol>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Square &#123;</span><br><span class="line">    kind: <span class="string">"square"</span>;</span><br><span class="line">    size: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> Rectangle &#123;</span><br><span class="line">    kind: <span class="string">"rectangle"</span>;</span><br><span class="line">    width: <span class="built_in">number</span>;</span><br><span class="line">    height: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> Circle &#123;</span><br><span class="line">    kind: <span class="string">"circle"</span>;</span><br><span class="line">    radius: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>首先我们声明了将要联合的接口。 每个接口都有 <code>kind</code>属性但有不同的字符串字面量类型。 <code>kind</code>属性称做 <em>可辨识的特征</em>或 <em>标签</em>。 其它的属性则特定于各个接口。 注意，目前各个接口间是没有联系的。 下面我们把它们联合到一起：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Shape = Square | Rectangle | Circle;</span><br></pre></td></tr></table></figure>

<p>现在我们使用可辨识联合:</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">area</span>(<span class="params">s: Shape</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">switch</span> (s.kind) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"square"</span>: <span class="keyword">return</span> s.size * s.size;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"rectangle"</span>: <span class="keyword">return</span> s.height * s.width;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"circle"</span>: <span class="keyword">return</span> <span class="built_in">Math</span>.PI * s.radius ** <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>完整性检查：</p>
<p>当没有涵盖所有可辨识联合的变化时，我们想让编译器可以通知我们。 比如，如果我们添加了 <code>Triangle</code>到 <code>Shape</code>，我们同时还需要更新 <code>area</code>:</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Shape = Square | Rectangle | Circle | Triangle;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">area</span>(<span class="params">s: Shape</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">switch</span> (s.kind) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"square"</span>: <span class="keyword">return</span> s.size * s.size;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"rectangle"</span>: <span class="keyword">return</span> s.height * s.width;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"circle"</span>: <span class="keyword">return</span> <span class="built_in">Math</span>.PI * s.radius ** <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// should error here - we didn't handle case "triangle"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>有两种方式可以实现。 首先是启用 <code>--strictNullChecks</code>并且指定一个返回值类型：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">area</span>(<span class="params">s: Shape</span>): <span class="title">number</span> </span>&#123; <span class="comment">// error: returns number | undefined</span></span><br><span class="line">    <span class="keyword">switch</span> (s.kind) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"square"</span>: <span class="keyword">return</span> s.size * s.size;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"rectangle"</span>: <span class="keyword">return</span> s.height * s.width;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"circle"</span>: <span class="keyword">return</span> <span class="built_in">Math</span>.PI * s.radius ** <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>因为 <code>switch</code>没有包涵所有情况，所以TypeScript认为这个函数有时候会返回 <code>undefined</code>。 如果你明确地指定了返回值类型为 <code>number</code>，那么你会看到一个错误，因为实际上返回值的类型为 <code>number | undefined</code>。 然而，这种方法存在些微妙之处且 <code>--strictNullChecks</code>对旧代码支持不好。</p>
<p>第二种方法使用 <code>never</code>类型，编译器用它来进行完整性检查：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">assertNever</span>(<span class="params">x: never</span>): <span class="title">never</span> </span>&#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"Unexpected object: "</span> + x);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">area</span>(<span class="params">s: Shape</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">switch</span> (s.kind) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"square"</span>: <span class="keyword">return</span> s.size * s.size;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"rectangle"</span>: <span class="keyword">return</span> s.height * s.width;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"circle"</span>: <span class="keyword">return</span> <span class="built_in">Math</span>.PI * s.radius ** <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">default</span>: <span class="keyword">return</span> assertNever(s); <span class="comment">// error here if there are missing cases</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里， <code>assertNever</code>检查 <code>s</code>是否为 <code>never</code>类型—即为除去所有可能情况后剩下的类型。 如果你忘记了某个case，那么 <code>s</code>将具有一个真实的类型并且你会得到一个错误。 这种方式需要你定义一个额外的函数，但是在你忘记某个case的时候也更加明显。</p>
<h3 id="多态的-this类型"><a href="#多态的-this类型" class="headerlink" title="多态的 this类型"></a>多态的 <code>this</code>类型</h3><p>多态的 <code>this</code>类型表示的是某个包含类或接口的 <em>子类型</em>。 这被称做 <em>F</em>-bounded多态性。 它能很容易的表现连贯接口间的继承，比如。 在计算器的例子里，在每个操作之后都返回 <code>this</code>类型：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> BasicCalculator &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">constructor</span>(<span class="params"><span class="keyword">protected</span> value: <span class="built_in">number</span> = 0</span>) &#123; &#125;</span><br><span class="line">    <span class="keyword">public</span> currentValue(): <span class="built_in">number</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> add(operand: <span class="built_in">number</span>): <span class="keyword">this</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.value += operand;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> multiply(operand: <span class="built_in">number</span>): <span class="keyword">this</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.value *= operand;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ... other operations go here ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> v = <span class="keyword">new</span> BasicCalculator(<span class="number">2</span>)</span><br><span class="line">            .multiply(<span class="number">5</span>)</span><br><span class="line">            .add(<span class="number">1</span>)</span><br><span class="line">            .currentValue();</span><br></pre></td></tr></table></figure>

<p>由于这个类使用了 <code>this</code>类型，你可以继承它，新的类可以直接使用之前的方法，不需要做任何的改变。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> ScientificCalculator <span class="keyword">extends</span> BasicCalculator &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">constructor</span>(<span class="params">value = 0</span>) &#123;</span><br><span class="line">        <span class="keyword">super</span>(value);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> sin() &#123;</span><br><span class="line">        <span class="keyword">this</span>.value = <span class="built_in">Math</span>.sin(<span class="keyword">this</span>.value);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ... other operations go here ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> v = <span class="keyword">new</span> ScientificCalculator(<span class="number">2</span>)</span><br><span class="line">        .multiply(<span class="number">5</span>)</span><br><span class="line">        .sin()</span><br><span class="line">        .add(<span class="number">1</span>)</span><br><span class="line">        .currentValue();</span><br></pre></td></tr></table></figure>

<p>如果没有 <code>this</code>类型， <code>ScientificCalculator</code>就不能够在继承 <code>BasicCalculator</code>的同时还保持接口的连贯性。 <code>multiply</code>将会返回 <code>BasicCalculator</code>，它并没有 <code>sin</code>方法。 然而，使用 <code>this</code>类型， <code>multiply</code>会返回 <code>this</code>，在这里就是 <code>ScientificCalculator</code>。</p>
<h3 id="索引类型"><a href="#索引类型" class="headerlink" title="索引类型"></a>索引类型</h3><p>使用索引类型，编译器就能够检查使用了动态属性名的代码。 例如，一个常见的JavaScript模式是从对象中选取属性的子集。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">pluck</span>(<span class="params">o, names</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> names.map(<span class="function"><span class="params">n</span> =&gt;</span> o[n]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面是如何在TypeScript里使用此函数，通过 <strong>索引类型查询</strong>和 <strong>索引访问</strong>操作符：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">pluck</span>&lt;<span class="title">T</span>, <span class="title">K</span> <span class="title">extends</span> <span class="title">keyof</span> <span class="title">T</span>&gt;(<span class="params">o: T, names: K[]</span>): <span class="title">T</span>[<span class="title">K</span>][] </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> names.map(<span class="function"><span class="params">n</span> =&gt;</span> o[n]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Person &#123;</span><br><span class="line">    name: <span class="built_in">string</span>;</span><br><span class="line">    age: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> person: Person = &#123;</span><br><span class="line">    name: <span class="string">'Jarid'</span>,</span><br><span class="line">    age: <span class="number">35</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">let</span> strings: <span class="built_in">string</span>[] = pluck(person, [<span class="string">'name'</span>]); <span class="comment">// ok, string[]</span></span><br></pre></td></tr></table></figure>

<p>编译器会检查 <code>name</code>是否真的是 <code>Person</code>的一个属性。 本例还引入了几个新的类型操作符。 首先是 <code>keyof T</code>， <strong>索引类型查询操作符</strong>。 对于任何类型 <code>T</code>， <code>keyof T</code>的结果为 <code>T</code>上已知的公共属性名的联合。 例如：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> personProps: keyof Person; <span class="comment">// 'name' | 'age'</span></span><br></pre></td></tr></table></figure>

<p><code>keyof Person</code>是完全可以与 <code>&#39;name&#39; | &#39;age&#39;</code>互相替换的。 不同的是如果你添加了其它的属性到 <code>Person</code>，例如 <code>address: string</code>，那么 <code>keyof Person</code>会自动变为 <code>&#39;name&#39; | &#39;age&#39; | &#39;address&#39;</code>。 你可以在像 <code>pluck</code>函数这类上下文里使用 <code>keyof</code>，因为在使用之前你并不清楚可能出现的属性名。 但编译器会检查你是否传入了正确的属性名给 <code>pluck</code>：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pluck(person, [<span class="string">'age'</span>, <span class="string">'unknown'</span>]); <span class="comment">// error, 'unknown' is not in 'name' | 'age'</span></span><br></pre></td></tr></table></figure>

<p>第二个操作符是 <code>T[K]</code>， <strong>索引访问操作符</strong>。 在这里，类型语法反映了表达式语法。 这意味着 <code>person[&#39;name&#39;]</code>具有类型 <code>Person[&#39;name&#39;]</code> — 在我们的例子里则为 <code>string</code>类型。 然而，就像索引类型查询一样，你可以在普通的上下文里使用 <code>T[K]</code>，这正是它的强大所在。 你只要确保类型变量 <code>K extends keyof T</code>就可以了。 例如下面 <code>getProperty</code>函数的例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getProperty</span>&lt;<span class="title">T</span>, <span class="title">K</span> <span class="title">extends</span> <span class="title">keyof</span> <span class="title">T</span>&gt;(<span class="params">o: T, name: K</span>): <span class="title">T</span>[<span class="title">K</span>] </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> o[name]; <span class="comment">// o[name] is of type T[K]</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>getProperty</code>里的 <code>o: T</code>和 <code>name: K</code>，意味着 <code>o[name]: T[K]</code>。 当你返回 <code>T[K]</code>的结果，编译器会实例化键的真实类型，因此 <code>getProperty</code>的返回值类型会随着你需要的属性改变。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> name: <span class="built_in">string</span> = getProperty(person, <span class="string">'name'</span>);</span><br><span class="line"><span class="keyword">let</span> age: <span class="built_in">number</span> = getProperty(person, <span class="string">'age'</span>);</span><br><span class="line"><span class="keyword">let</span> unknown = getProperty(person, <span class="string">'unknown'</span>); <span class="comment">// error, 'unknown' is not in 'name' | 'age'</span></span><br></pre></td></tr></table></figure>

<p>索引类型和字符串索引签名：</p>
<p><code>keyof</code>和 <code>T[K]</code>与字符串索引签名进行交互。 如果你有一个带有字符串索引签名的类型，那么 <code>keyof T</code>会是 <code>string</code>。 并且 <code>T[string]</code>为索引签名的类型：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Map&lt;T&gt; &#123;</span><br><span class="line">    [key: <span class="built_in">string</span>]: T;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> keys: keyof Map&lt;<span class="built_in">number</span>&gt;; <span class="comment">// string</span></span><br><span class="line"><span class="keyword">let</span> value: Map&lt;<span class="built_in">number</span>&gt;[<span class="string">'foo'</span>]; <span class="comment">// number</span></span><br></pre></td></tr></table></figure>

<h3 id="映射类型"><a href="#映射类型" class="headerlink" title="映射类型"></a>映射类型</h3><p>一个常见的任务是将一个已知的类型每个属性都变为可选的：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> PersonPartial &#123;</span><br><span class="line">    name?: <span class="built_in">string</span>;</span><br><span class="line">    age?: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>或者我们想要一个只读版本：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> PersonReadonly &#123;</span><br><span class="line">    readonly name: <span class="built_in">string</span>;</span><br><span class="line">    readonly age: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这在JavaScript里经常出现，TypeScript提供了从旧类型中创建新类型的一种方式 — <strong>映射类型</strong>。 在映射类型里，新类型以相同的形式去转换旧类型里每个属性。 例如，你可以令每个属性成为 <code>readonly</code>类型或可选的。 下面是一些例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Readonly&lt;T&gt; = &#123;</span><br><span class="line">    readonly [P <span class="keyword">in</span> keyof T]: T[P];</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">type</span> Partial&lt;T&gt; = &#123;</span><br><span class="line">    [P <span class="keyword">in</span> keyof T]?: T[P];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>像下面这样使用：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> PersonPartial = Partial&lt;Person&gt;;</span><br><span class="line"><span class="keyword">type</span> ReadonlyPerson = Readonly&lt;Person&gt;;</span><br></pre></td></tr></table></figure>

<p>下面来看看最简单的映射类型和它的组成部分：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Keys = <span class="string">'option1'</span> | <span class="string">'option2'</span>;</span><br><span class="line"><span class="keyword">type</span> Flags = &#123; [K <span class="keyword">in</span> Keys]: <span class="built_in">boolean</span> &#125;;</span><br></pre></td></tr></table></figure>

<p>它的语法与索引签名的语法类型，内部使用了 <code>for .. in</code>。 具有三个部分：</p>
<ol>
<li>类型变量 <code>K</code>，它会依次绑定到每个属性。</li>
<li>字符串字面量联合的 <code>Keys</code>，它包含了要迭代的属性名的集合。</li>
<li>属性的结果类型。</li>
</ol>
<p>在个简单的例子里， <code>Keys</code>是硬编码的的属性名列表并且属性类型永远是 <code>boolean</code>，因此这个映射类型等同于：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Flags = &#123;</span><br><span class="line">    option1: <span class="built_in">boolean</span>;</span><br><span class="line">    option2: <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在真正的应用里，可能不同于上面的 <code>Readonly</code>或 <code>Partial</code>。 它们会基于一些已存在的类型，且按照一定的方式转换字段。 这就是 <code>keyof</code>和索引访问类型要做的事情：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> NullablePerson = &#123; [P <span class="keyword">in</span> keyof Person]: Person[P] | <span class="literal">null</span> &#125;</span><br><span class="line"><span class="keyword">type</span> PartialPerson = &#123; [P <span class="keyword">in</span> keyof Person]?: Person[P] &#125;</span><br></pre></td></tr></table></figure>

<p>但它更有用的地方是可以有一些通用版本。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Nullable&lt;T&gt; = &#123; [P <span class="keyword">in</span> keyof T]: T[P] | <span class="literal">null</span> &#125;</span><br><span class="line"><span class="keyword">type</span> Partial&lt;T&gt; = &#123; [P <span class="keyword">in</span> keyof T]?: T[P] &#125;</span><br></pre></td></tr></table></figure>

<p>在这些例子里，属性列表是 <code>keyof T</code>且结果类型是 <code>T[P]</code>的变体。 这是使用通用映射类型的一个好模版。 因为这类转换是 <a href="https://en.wikipedia.org/wiki/Homomorphism" target="_blank" rel="noopener">同态</a>的，映射只作用于 <code>T</code>的属性而没有其它的。 编译器知道在添加任何新属性之前可以拷贝所有存在的属性修饰符。 例如，假设 <code>Person.name</code>是只读的，那么 <code>Partial&lt;Person&gt;.name</code>也将是只读的且为可选的。</p>
<p>下面是另一个例子， <code>T[P]</code>被包装在 <code>Proxy&lt;T&gt;</code>类里：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Proxy&lt;T&gt; = &#123;</span><br><span class="line">    <span class="keyword">get</span>(): T;</span><br><span class="line">    <span class="keyword">set</span>(value: T): <span class="built_in">void</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">type</span> Proxify&lt;T&gt; = &#123;</span><br><span class="line">    [P <span class="keyword">in</span> keyof T]: Proxy&lt;T[P]&gt;;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">proxify</span>&lt;<span class="title">T</span>&gt;(<span class="params">o: T</span>): <span class="title">Proxify</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">   <span class="comment">// ... wrap proxies ...</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> proxyProps = proxify(props);</span><br></pre></td></tr></table></figure>

<p>注意 <code>Readonly&lt;T&gt;</code>和 <code>Partial&lt;T&gt;</code>用处不小，因此它们与 <code>Pick</code>和 <code>Record</code>一同被包含进了TypeScript的标准库里：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Pick&lt;T, K <span class="keyword">extends</span> keyof T&gt; = &#123;</span><br><span class="line">    [P <span class="keyword">in</span> K]: T[P];</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">type</span> Record&lt;K <span class="keyword">extends</span> <span class="built_in">string</span>, T&gt; = &#123;</span><br><span class="line">    [P <span class="keyword">in</span> K]: T;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>Readonly</code>， <code>Partial</code>和 <code>Pick</code>是同态的，但 <code>Record</code>不是。 因为 <code>Record</code>并不需要输入类型来拷贝属性，所以它不属于同态：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> ThreeStringProps = Record&lt;<span class="string">'prop1'</span> | <span class="string">'prop2'</span> | <span class="string">'prop3'</span>, <span class="built_in">string</span>&gt;</span><br></pre></td></tr></table></figure>

<p>非同态类型本质上会创建新的属性，因此它们不会从它处拷贝属性修饰符。</p>
<p>由映射类型进行推断：</p>
<p>现在你了解了如何包装一个类型的属性，那么接下来就是如何拆包。 其实这也非常容易：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">unproxify</span>&lt;<span class="title">T</span>&gt;(<span class="params">t: Proxify&lt;T&gt;</span>): <span class="title">T</span> </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> result = &#123;&#125; <span class="keyword">as</span> T;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> k <span class="keyword">in</span> t) &#123;</span><br><span class="line">        result[k] = t[k].get();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> originalProps = unproxify(proxyProps);</span><br></pre></td></tr></table></figure>

<p>注意这个拆包推断只适用于同态的映射类型。 如果映射类型不是同态的，那么需要给拆包函数一个明确的类型参数。</p>
<p>预定义的有条件类型：</p>
<p>TypeScript 2.8在<code>lib.d.ts</code>里增加了一些预定义的有条件类型：</p>
<ul>
<li><code>Exclude&lt;T, U&gt;</code> – 从<code>T</code>中剔除可以赋值给<code>U</code>的类型。</li>
<li><code>Extract&lt;T, U&gt;</code> – 提取<code>T</code>中可以赋值给<code>U</code>的类型。</li>
<li><code>NonNullable&lt;T&gt;</code> – 从<code>T</code>中剔除<code>null</code>和<code>undefined</code>。</li>
<li><code>ReturnType&lt;T&gt;</code> – 获取函数返回值类型。</li>
<li><code>InstanceType&lt;T&gt;</code> – 获取构造函数类型的实例类型。</li>
</ul>
<p>示例：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> T00 = Exclude&lt;<span class="string">"a"</span> | <span class="string">"b"</span> | <span class="string">"c"</span> | <span class="string">"d"</span>, <span class="string">"a"</span> | <span class="string">"c"</span> | <span class="string">"f"</span>&gt;;  <span class="comment">// "b" | "d"</span></span><br><span class="line"><span class="keyword">type</span> T01 = Extract&lt;<span class="string">"a"</span> | <span class="string">"b"</span> | <span class="string">"c"</span> | <span class="string">"d"</span>, <span class="string">"a"</span> | <span class="string">"c"</span> | <span class="string">"f"</span>&gt;;  <span class="comment">// "a" | "c"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> T02 = Exclude&lt;<span class="built_in">string</span> | <span class="built_in">number</span> | <span class="function">(<span class="params">(<span class="params"></span>) =&gt; <span class="built_in">void</span></span>), <span class="params">Function</span>&gt;;  // <span class="params">string</span> | <span class="params">number</span></span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T03</span> = <span class="params">Extract</span>&lt;<span class="params">string</span> | <span class="params">number</span> | (<span class="params">(<span class="params"></span>) =&gt; <span class="built_in">void</span></span>), <span class="params">Function</span>&gt;;  // <span class="params">()</span> =&gt;</span> <span class="built_in">void</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> T04 = NonNullable&lt;<span class="built_in">string</span> | <span class="built_in">number</span> | <span class="literal">undefined</span>&gt;;  <span class="comment">// string | number</span></span><br><span class="line"><span class="keyword">type</span> T05 = NonNullable&lt;<span class="function">(<span class="params">(<span class="params"></span>) =&gt; <span class="built_in">string</span></span>) | <span class="params">string</span>[] | <span class="params">null</span> | <span class="params">undefined</span>&gt;;  // (<span class="params">(<span class="params"></span>) =&gt; <span class="built_in">string</span></span>) | <span class="params">string</span>[]</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"><span class="params">function</span> <span class="params">f1</span>(<span class="params">s: <span class="built_in">string</span></span>) &#123;</span></span><br><span class="line"><span class="function">    <span class="params">return</span> &#123; <span class="params">a</span>: 1, <span class="params">b</span>: <span class="params">s</span> &#125;;</span></span><br><span class="line"><span class="function">&#125;</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"><span class="params">class</span> <span class="params">C</span> &#123;</span></span><br><span class="line"><span class="function">    <span class="params">x</span> = 0;</span></span><br><span class="line"><span class="function">    <span class="params">y</span> = 0;</span></span><br><span class="line"><span class="function">&#125;</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T10</span> = <span class="params">ReturnType</span>&lt;<span class="params">()</span> =&gt;</span> <span class="built_in">string</span>&gt;;  <span class="comment">// string</span></span><br><span class="line"><span class="keyword">type</span> T11 = ReturnType&lt;<span class="function">(<span class="params">s: <span class="built_in">string</span></span>) =&gt;</span> <span class="built_in">void</span>&gt;;  <span class="comment">// void</span></span><br><span class="line"><span class="keyword">type</span> T12 = ReturnType&lt;<span class="function">(<span class="params">&lt;T&gt;(<span class="params"></span>) =&gt; T</span>)&gt;;  // &#123;&#125;</span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T13</span> = <span class="params">ReturnType</span>&lt;(<span class="params">&lt;T <span class="keyword">extends</span> U, U <span class="keyword">extends</span> <span class="built_in">number</span>[]&gt;(<span class="params"></span>) =&gt; T</span>)&gt;;  // <span class="params">number</span>[]</span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T14</span> = <span class="params">ReturnType</span>&lt;<span class="params">typeof</span> <span class="params">f1</span>&gt;;  // &#123; <span class="params">a</span>: <span class="params">number</span>, <span class="params">b</span>: <span class="params">string</span> &#125;</span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T15</span> = <span class="params">ReturnType</span>&lt;<span class="params">any</span>&gt;;  // <span class="params">any</span></span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T16</span> = <span class="params">ReturnType</span>&lt;<span class="params">never</span>&gt;;  // <span class="params">any</span></span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T17</span> = <span class="params">ReturnType</span>&lt;<span class="params">string</span>&gt;;  // <span class="params">Error</span></span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T18</span> = <span class="params">ReturnType</span>&lt;<span class="params">Function</span>&gt;;  // <span class="params">Error</span></span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T20</span> = <span class="params">InstanceType</span>&lt;<span class="params">typeof</span> <span class="params">C</span>&gt;;  // <span class="params">C</span></span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T21</span> = <span class="params">InstanceType</span>&lt;<span class="params">any</span>&gt;;  // <span class="params">any</span></span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T22</span> = <span class="params">InstanceType</span>&lt;<span class="params">never</span>&gt;;  // <span class="params">any</span></span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T23</span> = <span class="params">InstanceType</span>&lt;<span class="params">string</span>&gt;;  // <span class="params">Error</span></span></span><br><span class="line"><span class="function"><span class="params">type</span> <span class="params">T24</span> = <span class="params">InstanceType</span>&lt;<span class="params">Function</span>&gt;;  // <span class="params">Error</span></span></span><br></pre></td></tr></table></figure>

<p>注意：<code>Exclude</code>类型是<a href="https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458" target="_blank" rel="noopener">建议的</a><code>Diff</code>类型的一种实现。我们使用<code>Exclude</code>这个名字是为了避免破坏已经定义了<code>Diff</code>的代码，并且我们感觉这个名字能更好地表达类型的语义。我们没有增加<code>Omit&lt;T, K&gt;</code>类型，因为它可以很容易的用<code>Pick&lt;T, Exclude&lt;keyof T, K&gt;&gt;</code>来表示。</p>
<h2 id="Symbols"><a href="#Symbols" class="headerlink" title="Symbols"></a>Symbols</h2><p>自ECMAScript 2015起，<code>symbol</code>成为了一种新的原生类型，就像<code>number</code>和<code>string</code>一样。</p>
<p><code>symbol</code>类型的值是通过<code>Symbol</code>构造函数创建的。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> sym1 = Symbol();</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> sym2 = Symbol(<span class="string">"key"</span>); <span class="comment">// 可选的字符串key</span></span><br></pre></td></tr></table></figure>

<p>Symbols是不可改变且唯一的。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> sym2 = Symbol(<span class="string">"key"</span>);</span><br><span class="line"><span class="keyword">let</span> sym3 = Symbol(<span class="string">"key"</span>);</span><br><span class="line"></span><br><span class="line">sym2 === sym3; <span class="comment">// false, symbols是唯一的</span></span><br></pre></td></tr></table></figure>

<p>像字符串一样，symbols也可以被用做对象属性的键。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> sym = Symbol();</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> obj = &#123;</span><br><span class="line">    [sym]: <span class="string">"value"</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(obj[sym]); <span class="comment">// "value"</span></span><br></pre></td></tr></table></figure>

<p>Symbols也可以与计算出的属性名声明相结合来声明对象的属性和类成员。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> getClassNameSymbol = Symbol();</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> C &#123;</span><br><span class="line">    [getClassNameSymbol]()&#123;</span><br><span class="line">       <span class="keyword">return</span> <span class="string">"C"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> c = <span class="keyword">new</span> C();</span><br><span class="line"><span class="keyword">let</span> className = c[getClassNameSymbol](); <span class="comment">// "C"</span></span><br></pre></td></tr></table></figure>

<h3 id="众所周知的Symbols"><a href="#众所周知的Symbols" class="headerlink" title="众所周知的Symbols"></a>众所周知的Symbols</h3><p>除了用户定义的symbols，还有一些已经众所周知的内置symbols。 内置symbols用来表示语言内部的行为。</p>
<p>以下为这些symbols的列表：</p>
<p><code>Symbol.hasInstance</code></p>
<p>方法，会被<code>instanceof</code>运算符调用。构造器对象用来识别一个对象是否是其实例。</p>
<p><code>Symbol.isConcatSpreadable</code></p>
<p>布尔值，表示当在一个对象上调用<code>Array.prototype.concat</code>时，这个对象的数组元素是否可展开。</p>
<p><code>Symbol.iterator</code></p>
<p>方法，被<code>for-of</code>语句调用。返回对象的默认迭代器。</p>
<p><code>Symbol.match</code></p>
<p>方法，被<code>String.prototype.match</code>调用。正则表达式用来匹配字符串。</p>
<p><code>Symbol.replace</code></p>
<p>方法，被<code>String.prototype.replace</code>调用。正则表达式用来替换字符串中匹配的子串。</p>
<p><code>Symbol.search</code></p>
<p>方法，被<code>String.prototype.search</code>调用。正则表达式返回被匹配部分在字符串中的索引。</p>
<p><code>Symbol.species</code></p>
<p>函数值，为一个构造函数。用来创建派生对象。</p>
<p><code>Symbol.split</code></p>
<p>方法，被<code>String.prototype.split</code>调用。正则表达式来用分割字符串。</p>
<p><code>Symbol.toPrimitive</code></p>
<p>方法，被<code>ToPrimitive</code>抽象操作调用。把对象转换为相应的原始值。</p>
<p><code>Symbol.toStringTag</code></p>
<p>方法，被内置方法<code>Object.prototype.toString</code>调用。返回创建对象时默认的字符串描述。</p>
<p><code>Symbol.unscopables</code></p>
<p>对象，它自己拥有的属性会被<code>with</code>作用域排除在外。</p>
<h2 id="迭代器和生成器"><a href="#迭代器和生成器" class="headerlink" title="迭代器和生成器"></a>迭代器和生成器</h2><p>当一个对象实现了<a href="https://www.tslang.cn/docs/handbook/symbols.html#symboliterator" target="_blank" rel="noopener"><code>Symbol.iterator</code></a>属性时，我们认为它是可迭代的。 一些内置的类型如 <code>Array</code>，<code>Map</code>，<code>Set</code>，<code>String</code>，<code>Int32Array</code>，<code>Uint32Array</code>等都已经实现了各自的<code>Symbol.iterator</code>。 对象上的 <code>Symbol.iterator</code>函数负责返回供迭代的值。</p>
<p><code>for..of</code> 语句</p>
<p><code>for..of</code>会遍历可迭代的对象，调用对象上的<code>Symbol.iterator</code>方法。 下面是在数组上使用 <code>for..of</code>的简单例子：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> someArray = [<span class="number">1</span>, <span class="string">"string"</span>, <span class="literal">false</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> entry of someArray) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(entry); <span class="comment">// 1, "string", false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>for..of</code> vs. <code>for..in</code> 语句</p>
<p><code>for..of</code>和<code>for..in</code>均可迭代一个列表；但是用于迭代的值却不同，<code>for..in</code>迭代的是对象的 <em>键</em> 的列表，而<code>for..of</code>则迭代对象的键对应的值。</p>
<p>下面的例子展示了两者之间的区别：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> list = [<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">in</span> list) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(i); <span class="comment">// "0", "1", "2",</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i of list) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(i); <span class="comment">// "4", "5", "6"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>另一个区别是<code>for..in</code>可以操作任何对象；它提供了查看对象属性的一种方法。 但是 <code>for..of</code>关注于迭代对象的值。内置对象<code>Map</code>和<code>Set</code>已经实现了<code>Symbol.iterator</code>方法，让我们可以访问它们保存的值。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> pets = <span class="keyword">new</span> Set([<span class="string">"Cat"</span>, <span class="string">"Dog"</span>, <span class="string">"Hamster"</span>]);</span><br><span class="line">pets[<span class="string">"species"</span>] = <span class="string">"mammals"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> pet <span class="keyword">in</span> pets) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(pet); <span class="comment">// "species"</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> pet of pets) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(pet); <span class="comment">// "Cat", "Dog", "Hamster"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="代码生成"><a href="#代码生成" class="headerlink" title="代码生成"></a>代码生成</h3><p>目标为 ES5 和 ES3：</p>
<p>当生成目标为ES5或ES3，迭代器只允许在<code>Array</code>类型上使用。 在非数组值上使用 <code>for..of</code>语句会得到一个错误，就算这些非数组值已经实现了<code>Symbol.iterator</code>属性。</p>
<p>编译器会生成一个简单的<code>for</code>循环做为<code>for..of</code>循环，比如：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> num of numbers) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(num);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>生成的代码为：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> numbers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> _i = <span class="number">0</span>; _i &lt; numbers.length; _i++) &#123;</span><br><span class="line">    <span class="keyword">var</span> num = numbers[_i];</span><br><span class="line">    <span class="built_in">console</span>.log(num);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>目标为 ECMAScript 2015 或更高：</p>
<p>当目标为兼容ECMAScipt 2015的引擎时，编译器会生成相应引擎的<code>for..of</code>内置迭代器实现方式。</p>
<h2 id="模块"><a href="#模块" class="headerlink" title="模块"></a>模块</h2><blockquote>
<p><strong>关于术语的一点说明:</strong> 请务必注意一点，TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”，这是为了与 <a href="http://www.ecma-international.org/ecma-262/6.0/" target="_blank" rel="noopener">ECMAScript 2015</a>里的术语保持一致，(也就是说 <code>module X {</code> 相当于现在推荐的写法 <code>namespace X {</code>)。</p>
</blockquote>
<p>从ECMAScript 2015开始，JavaScript引入了模块的概念。TypeScript也沿用这个概念。</p>
<p>模块在其自身的作用域里执行，而不是在全局作用域里；这意味着定义在一个模块里的变量，函数，类等等在模块外部是不可见的，除非你明确地使用<a href="https://www.tslang.cn/docs/handbook/modules.html#export" target="_blank" rel="noopener"><code>export</code>形式</a>之一导出它们。 相反，如果想使用其它模块导出的变量，函数，类，接口等的时候，你必须要导入它们，可以使用 <a href="https://www.tslang.cn/docs/handbook/modules.html#import" target="_blank" rel="noopener"><code>import</code>形式</a>之一。</p>
<p>模块是自声明的；两个模块之间的关系是通过在文件级别上使用imports和exports建立的。</p>
<p>模块使用模块加载器去导入其它的模块。 在运行时，模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。 大家最熟知的JavaScript模块加载器是服务于Node.js的 <a href="https://en.wikipedia.org/wiki/CommonJS" target="_blank" rel="noopener">CommonJS</a>和服务于Web应用的<a href="http://requirejs.org/" target="_blank" rel="noopener">Require.js</a>。</p>
<p>TypeScript与ECMAScript 2015一样，任何包含顶级<code>import</code>或者<code>export</code>的文件都被当成一个模块。相反地，如果一个文件不带有顶级的<code>import</code>或者<code>export</code>声明，那么它的内容被视为全局可见的（因此对模块也是可见的）。</p>
<h3 id="导出"><a href="#导出" class="headerlink" title="导出"></a>导出</h3><p>任何声明（比如变量，函数，类，类型别名或接口）都能够通过添加<code>export</code>关键字来导出。</p>
<p>Validation.ts：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> StringValidator &#123;</span><br><span class="line">    isAcceptable(s: <span class="built_in">string</span>): <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>ZipCodeValidator.ts：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> numberRegexp = <span class="regexp">/^[0-9]+$/</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> ZipCodeValidator <span class="keyword">implements</span> StringValidator &#123;</span><br><span class="line">    isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> s.length === <span class="number">5</span> &amp;&amp; numberRegexp.test(s);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>导出语句：</p>
<p>导出语句很便利，因为我们可能需要对导出的部分重命名，所以上面的例子可以这样改写：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> ZipCodeValidator <span class="keyword">implements</span> StringValidator &#123;</span><br><span class="line">    isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> s.length === <span class="number">5</span> &amp;&amp; numberRegexp.test(s);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> &#123; ZipCodeValidator &#125;;</span><br><span class="line"><span class="keyword">export</span> &#123; ZipCodeValidator <span class="keyword">as</span> mainValidator &#125;;</span><br></pre></td></tr></table></figure>

<p>重新导出：</p>
<p>我们经常会去扩展其它模块，并且只导出那个模块的部分内容。 重新导出功能并不会在当前模块导入那个模块或定义一个新的局部变量。</p>
<p>ParseIntBasedZipCodeValidator.ts：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> ParseIntBasedZipCodeValidator &#123;</span><br><span class="line">    isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> s.length === <span class="number">5</span> &amp;&amp; <span class="built_in">parseInt</span>(s).toString() === s;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 导出原先的验证器但做了重命名</span></span><br><span class="line"><span class="keyword">export</span> &#123;ZipCodeValidator <span class="keyword">as</span> RegExpBasedZipCodeValidator&#125; <span class="keyword">from</span> <span class="string">"./ZipCodeValidator"</span>;</span><br></pre></td></tr></table></figure>

<p><strong>或者一个模块可以包裹多个模块，并把他们导出的内容联合在一起通过语法：<code>export * from &quot;module&quot;</code>。</strong></p>
<p>AllValidators.ts：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">"./StringValidator"</span>; <span class="comment">// exports interface StringValidator</span></span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">"./LettersOnlyValidator"</span>; <span class="comment">// exports class LettersOnlyValidator</span></span><br><span class="line"><span class="keyword">export</span> * <span class="keyword">from</span> <span class="string">"./ZipCodeValidator"</span>;  <span class="comment">// exports class ZipCodeValidator</span></span><br></pre></td></tr></table></figure>

<h3 id="导入"><a href="#导入" class="headerlink" title="导入"></a>导入</h3><p>模块的导入操作与导出一样简单。 可以使用以下 <code>import</code>形式之一来导入其它模块中的导出内容。</p>
<p>导入一个模块中的某个导出内容：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; ZipCodeValidator &#125; <span class="keyword">from</span> <span class="string">"./ZipCodeValidator"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myValidator = <span class="keyword">new</span> ZipCodeValidator();</span><br></pre></td></tr></table></figure>

<p>可以对导入内容重命名</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; ZipCodeValidator <span class="keyword">as</span> ZCV &#125; <span class="keyword">from</span> <span class="string">"./ZipCodeValidator"</span>;</span><br><span class="line"><span class="keyword">let</span> myValidator = <span class="keyword">new</span> ZCV();</span><br></pre></td></tr></table></figure>

<p>将整个模块导入到一个变量，并通过它来访问模块的导出部分：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> validator <span class="keyword">from</span> <span class="string">"./ZipCodeValidator"</span>;</span><br><span class="line"><span class="keyword">let</span> myValidator = <span class="keyword">new</span> validator.ZipCodeValidator();</span><br></pre></td></tr></table></figure>

<p>具有副作用的导入模块：</p>
<p>尽管不推荐这么做，一些模块会设置一些全局状态供其它模块使用。 这些模块可能没有任何的导出或用户根本就不关注它的导出。 使用下面的方法来导入这类模块：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"./my-module.js"</span>;</span><br></pre></td></tr></table></figure>

<h3 id="默认导出"><a href="#默认导出" class="headerlink" title="默认导出"></a>默认导出</h3><p>每个模块都可以有一个<code>default</code>导出。 默认导出使用 <code>default</code>关键字标记；并且一个模块只能够有一个<code>default</code>导出。 需要使用一种特殊的导入形式来导入 <code>default</code>导出。</p>
<p><code>default</code>导出十分便利。 比如，像JQuery这样的类库可能有一个默认导出 <code>jQuery</code>或<code>$</code>，并且我们基本上也会使用同样的名字<code>jQuery</code>或<code>$</code>导出JQuery。</p>
<p>JQuery.d.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">let</span> $: JQuery;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> $;</span><br></pre></td></tr></table></figure>

<p>App.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> $ <span class="keyword">from</span> <span class="string">"JQuery"</span>;</span><br><span class="line"></span><br><span class="line">$(<span class="string">"button.continue"</span>).html( <span class="string">"Next Step..."</span> );</span><br></pre></td></tr></table></figure>

<p>类和函数声明可以直接被标记为默认导出。 标记为默认导出的类和函数的名字是可以省略的。</p>
<p>ZipCodeValidator.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> ZipCodeValidator &#123;</span><br><span class="line">    <span class="keyword">static</span> numberRegexp = <span class="regexp">/^[0-9]+$/</span>;</span><br><span class="line">    isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> s.length === <span class="number">5</span> &amp;&amp; ZipCodeValidator.numberRegexp.test(s);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Test.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> validator <span class="keyword">from</span> <span class="string">"./ZipCodeValidator"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> myValidator = <span class="keyword">new</span> validator();</span><br></pre></td></tr></table></figure>

<p>或者</p>
<p>StaticZipCodeValidator.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> numberRegexp = <span class="regexp">/^[0-9]+$/</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> (<span class="params">s: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> s.length === <span class="number">5</span> &amp;&amp; numberRegexp.test(s);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Test.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> validate <span class="keyword">from</span> <span class="string">"./StaticZipCodeValidator"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> strings = [<span class="string">"Hello"</span>, <span class="string">"98052"</span>, <span class="string">"101"</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// Use function validate</span></span><br><span class="line">strings.forEach(<span class="function"><span class="params">s</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">`"<span class="subst">$&#123;s&#125;</span>" <span class="subst">$&#123;validate(s) ? <span class="string">" matches"</span> : <span class="string">" does not match"</span>&#125;</span>`</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p><code>default</code>导出也可以是一个值</p>
<p>OneTwoThree.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="string">"123"</span>;</span><br></pre></td></tr></table></figure>

<p>Log.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> num <span class="keyword">from</span> <span class="string">"./OneTwoThree"</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(num); <span class="comment">// "123"</span></span><br></pre></td></tr></table></figure>

<p><code>export =</code> 和 <code>import = require()</code></p>
<p>CommonJS和AMD的环境里都有一个<code>exports</code>变量，这个变量包含了一个模块的所有导出内容。</p>
<p>CommonJS和AMD的<code>exports</code>都可以被赋值为一个<code>对象</code>, 这种情况下其作用就类似于 es6 语法里的默认导出，即 <code>export default</code>语法了。虽然作用相似，但是 <code>export default</code> 语法并不能兼容CommonJS和AMD的<code>exports</code>。</p>
<p>为了支持CommonJS和AMD的<code>exports</code>, TypeScript提供了<code>export =</code>语法。</p>
<p><code>export =</code>语法定义一个模块的导出<code>对象</code>。 这里的<code>对象</code>一词指的是类，接口，命名空间，函数或枚举。</p>
<p>若使用<code>export =</code>导出一个模块，则必须使用TypeScript的特定语法<code>import module = require(&quot;module&quot;)</code>来导入此模块。</p>
<p>ZipCodeValidator.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> numberRegexp = <span class="regexp">/^[0-9]+$/</span>;</span><br><span class="line"><span class="keyword">class</span> ZipCodeValidator &#123;</span><br><span class="line">    isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> s.length === <span class="number">5</span> &amp;&amp; numberRegexp.test(s);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> = ZipCodeValidator;</span><br></pre></td></tr></table></figure>

<p>Test.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> zip = <span class="built_in">require</span>(<span class="string">"./ZipCodeValidator"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Some samples to try</span></span><br><span class="line"><span class="keyword">let</span> strings = [<span class="string">"Hello"</span>, <span class="string">"98052"</span>, <span class="string">"101"</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// Validators to use</span></span><br><span class="line"><span class="keyword">let</span> validator = <span class="keyword">new</span> zip();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Show whether each string passed each validator</span></span><br><span class="line">strings.forEach(<span class="function"><span class="params">s</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">`"<span class="subst">$&#123; s &#125;</span>" - <span class="subst">$&#123; validator.isAcceptable(s) ? <span class="string">"matches"</span> : <span class="string">"does not match"</span> &#125;</span>`</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h3 id="生成模块代码"><a href="#生成模块代码" class="headerlink" title="生成模块代码"></a>生成模块代码</h3><p>根据编译时指定的模块目标参数，编译器会生成相应的供Node.js (<a href="http://wiki.commonjs.org/wiki/CommonJS" target="_blank" rel="noopener">CommonJS</a>)，Require.js (<a href="https://github.com/amdjs/amdjs-api/wiki/AMD" target="_blank" rel="noopener">AMD</a>)，<a href="https://github.com/umdjs/umd" target="_blank" rel="noopener">UMD</a>，<a href="https://github.com/systemjs/systemjs" target="_blank" rel="noopener">SystemJS</a>或<a href="http://www.ecma-international.org/ecma-262/6.0/#sec-modules" target="_blank" rel="noopener">ECMAScript 2015 native modules</a> (ES6)模块加载系统使用的代码。 想要了解生成代码中 <code>define</code>，<code>require</code> 和 <code>register</code>的意义，请参考相应模块加载器的文档。</p>
<p>下面的例子说明了导入导出语句里使用的名字是怎么转换为相应的模块加载器代码的。</p>
<p>SimpleModule.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> m = <span class="built_in">require</span>(<span class="string">"mod"</span>);</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">let</span> t = m.something + <span class="number">1</span>;</span><br></pre></td></tr></table></figure>

<p>AMD / RequireJS SimpleModule.js</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">define([<span class="string">"require"</span>, <span class="string">"exports"</span>, <span class="string">"./mod"</span>], <span class="function"><span class="keyword">function</span> (<span class="params">require, exports, mod_1</span>) </span>&#123;</span><br><span class="line">    exports.t = mod_1.something + <span class="number">1</span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>CommonJS / Node SimpleModule.js</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> mod_1 = <span class="built_in">require</span>(<span class="string">"./mod"</span>);</span><br><span class="line">exports.t = mod_1.something + <span class="number">1</span>;</span><br></pre></td></tr></table></figure>

<p>UMD SimpleModule.js</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">(<span class="function"><span class="keyword">function</span> (<span class="params">factory</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> <span class="built_in">module</span> === <span class="string">"object"</span> &amp;&amp; <span class="keyword">typeof</span> <span class="built_in">module</span>.exports === <span class="string">"object"</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> v = factory(<span class="built_in">require</span>, exports); <span class="keyword">if</span> (v !== <span class="literal">undefined</span>) <span class="built_in">module</span>.exports = v;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> define === <span class="string">"function"</span> &amp;&amp; define.amd) &#123;</span><br><span class="line">        define([<span class="string">"require"</span>, <span class="string">"exports"</span>, <span class="string">"./mod"</span>], factory);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)(<span class="function"><span class="keyword">function</span> (<span class="params">require, exports</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> mod_1 = <span class="built_in">require</span>(<span class="string">"./mod"</span>);</span><br><span class="line">    exports.t = mod_1.something + <span class="number">1</span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>System SimpleModule.js</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">System.register([<span class="string">"./mod"</span>], <span class="function"><span class="keyword">function</span>(<span class="params">exports_1</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> mod_1;</span><br><span class="line">    <span class="keyword">let</span> t;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        setters:[</span><br><span class="line">            <span class="function"><span class="keyword">function</span> (<span class="params">mod_1_1</span>) </span>&#123;</span><br><span class="line">                mod_1 = mod_1_1;</span><br><span class="line">            &#125;],</span><br><span class="line">        execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">            exports_1(<span class="string">"t"</span>, t = mod_1.something + <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>Native ECMAScript 2015 modules SimpleModule.js</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; something &#125; <span class="keyword">from</span> <span class="string">"./mod"</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">let</span> t = something + <span class="number">1</span>;</span><br></pre></td></tr></table></figure>

<h3 id="简单示例"><a href="#简单示例" class="headerlink" title="简单示例"></a>简单示例</h3><p>下面我们来整理一下前面的验证器实现，每个模块只有一个命名的导出。</p>
<p>为了编译，我们必需要在命令行上指定一个模块目标。对于Node.js来说，使用<code>--module commonjs</code>； 对于Require.js来说，使用<code>--module amd</code>。比如：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tsc --module commonjs Test.ts</span><br></pre></td></tr></table></figure>

<p>编译完成后，每个模块会生成一个单独的<code>.js</code>文件。 好比使用了reference标签，编译器会根据 <code>import</code>语句编译相应的文件。</p>
<p>Validation.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> StringValidator &#123;</span><br><span class="line">    isAcceptable(s: <span class="built_in">string</span>): <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>LettersOnlyValidator.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; StringValidator &#125; <span class="keyword">from</span> <span class="string">"./Validation"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> lettersRegexp = <span class="regexp">/^[A-Za-z]+$/</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> LettersOnlyValidator <span class="keyword">implements</span> StringValidator &#123;</span><br><span class="line">    isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> lettersRegexp.test(s);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>ZipCodeValidator.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; StringValidator &#125; <span class="keyword">from</span> <span class="string">"./Validation"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> numberRegexp = <span class="regexp">/^[0-9]+$/</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> ZipCodeValidator <span class="keyword">implements</span> StringValidator &#123;</span><br><span class="line">    isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> s.length === <span class="number">5</span> &amp;&amp; numberRegexp.test(s);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Test.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; StringValidator &#125; <span class="keyword">from</span> <span class="string">"./Validation"</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; ZipCodeValidator &#125; <span class="keyword">from</span> <span class="string">"./ZipCodeValidator"</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; LettersOnlyValidator &#125; <span class="keyword">from</span> <span class="string">"./LettersOnlyValidator"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Some samples to try</span></span><br><span class="line"><span class="keyword">let</span> strings = [<span class="string">"Hello"</span>, <span class="string">"98052"</span>, <span class="string">"101"</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// Validators to use</span></span><br><span class="line"><span class="keyword">let</span> validators: &#123; [s: <span class="built_in">string</span>]: StringValidator; &#125; = &#123;&#125;;</span><br><span class="line">validators[<span class="string">"ZIP code"</span>] = <span class="keyword">new</span> ZipCodeValidator();</span><br><span class="line">validators[<span class="string">"Letters only"</span>] = <span class="keyword">new</span> LettersOnlyValidator();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Show whether each string passed each validator</span></span><br><span class="line">strings.forEach(<span class="function"><span class="params">s</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> name <span class="keyword">in</span> validators) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">`"<span class="subst">$&#123; s &#125;</span>" - <span class="subst">$&#123; validators[name].isAcceptable(s) ? <span class="string">"matches"</span> : <span class="string">"does not match"</span> &#125;</span> <span class="subst">$&#123; name &#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h4 id="可选的模块加载和其它高级加载场景"><a href="#可选的模块加载和其它高级加载场景" class="headerlink" title="可选的模块加载和其它高级加载场景"></a>可选的模块加载和其它高级加载场景</h4><p>有时候，你只想在某种条件下才加载某个模块。 在TypeScript里，使用下面的方式来实现它和其它的高级加载场景，我们可以直接调用模块加载器并且可以保证类型完全。</p>
<p>编译器会检测是否每个模块都会在生成的JavaScript中用到。 如果一个模块标识符只在类型注解部分使用，并且完全没有在表达式中使用时，就不会生成 <code>require</code>这个模块的代码。 省略掉没有用到的引用对性能提升是很有益的，并同时提供了选择性加载模块的能力。</p>
<p>这种模式的核心是<code>import id = require(&quot;...&quot;)</code>语句可以让我们访问模块导出的类型。 模块加载器会被动态调用（通过 <code>require</code>），就像下面<code>if</code>代码块里那样。 它利用了省略引用的优化，所以模块只在被需要时加载。 为了让这个模块工作，一定要注意 <code>import</code>定义的标识符只能在表示类型处使用（不能在会转换成JavaScript的地方）。</p>
<p>为了确保类型安全性，我们可以使用<code>typeof</code>关键字。 <code>typeof</code>关键字，当在表示类型的地方使用时，会得出一个类型值，这里就表示模块的类型。</p>
<p>示例：Node.js里的动态模块加载：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="function"><span class="keyword">function</span> <span class="title">require</span>(<span class="params">moduleName: <span class="built_in">string</span></span>): <span class="title">any</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; ZipCodeValidator <span class="keyword">as</span> Zip &#125; <span class="keyword">from</span> <span class="string">"./ZipCodeValidator"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (needZipValidation) &#123;</span><br><span class="line">    <span class="keyword">let</span> ZipCodeValidator: <span class="keyword">typeof</span> Zip = <span class="built_in">require</span>(<span class="string">"./ZipCodeValidator"</span>);</span><br><span class="line">    <span class="keyword">let</span> validator = <span class="keyword">new</span> ZipCodeValidator();</span><br><span class="line">    <span class="keyword">if</span> (validator.isAcceptable(<span class="string">"..."</span>)) &#123; <span class="comment">/* ... */</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>示例：require.js里的动态模块加载</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="function"><span class="keyword">function</span> <span class="title">require</span>(<span class="params">moduleNames: <span class="built_in">string</span>[], onLoad: (...args: <span class="built_in">any</span>[]) =&gt; <span class="built_in">void</span></span>): <span class="title">void</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> Zip <span class="keyword">from</span> <span class="string">"./ZipCodeValidator"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (needZipValidation) &#123;</span><br><span class="line">    <span class="built_in">require</span>([<span class="string">"./ZipCodeValidator"</span>], <span class="function">(<span class="params">ZipCodeValidator: <span class="keyword">typeof</span> Zip</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> validator = <span class="keyword">new</span> ZipCodeValidator.ZipCodeValidator();</span><br><span class="line">        <span class="keyword">if</span> (validator.isAcceptable(<span class="string">"..."</span>)) &#123; <span class="comment">/* ... */</span> &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>示例：System.js里的动态模块加载</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">const</span> System: <span class="built_in">any</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; ZipCodeValidator <span class="keyword">as</span> Zip &#125; <span class="keyword">from</span> <span class="string">"./ZipCodeValidator"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (needZipValidation) &#123;</span><br><span class="line">    System.import(<span class="string">"./ZipCodeValidator"</span>).then(<span class="function">(<span class="params">ZipCodeValidator: <span class="keyword">typeof</span> Zip</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">var</span> x = <span class="keyword">new</span> ZipCodeValidator();</span><br><span class="line">        <span class="keyword">if</span> (x.isAcceptable(<span class="string">"..."</span>)) &#123; <span class="comment">/* ... */</span> &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="其它的JavaScript库"><a href="#其它的JavaScript库" class="headerlink" title="其它的JavaScript库"></a>其它的JavaScript库</h3><p>要想描述非TypeScript编写的类库的类型，我们需要声明类库所暴露出的API。</p>
<p>我们叫它声明因为它不是“外部程序”的具体实现。 它们通常是在 <code>.d.ts</code>文件里定义的。 如果你熟悉C/C++，你可以把它们当做 <code>.h</code>文件。 让我们看一些例子。</p>
<h4 id="外部模块"><a href="#外部模块" class="headerlink" title="外部模块"></a>外部模块</h4><p>在Node.js里大部分工作是通过加载一个或多个模块实现的。 我们可以使用顶级的 <code>export</code>声明来为每个模块都定义一个<code>.d.ts</code>文件，但最好还是写在一个大的<code>.d.ts</code>文件里。 我们使用与构造一个外部命名空间相似的方法，但是这里使用 <code>module</code>关键字并且把名字用引号括起来，方便之后<code>import</code>。 例如：</p>
<p>node.d.ts (simplified excerpt)</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> "url" &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">interface</span> Url &#123;</span><br><span class="line">        protocol?: <span class="built_in">string</span>;</span><br><span class="line">        hostname?: <span class="built_in">string</span>;</span><br><span class="line">        pathname?: <span class="built_in">string</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">parse</span>(<span class="params">urlStr: <span class="built_in">string</span>, parseQueryString?, slashesDenoteHost?</span>): <span class="title">Url</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> "path" &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">normalize</span>(<span class="params">p: <span class="built_in">string</span></span>): <span class="title">string</span></span>;</span><br><span class="line">    <span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">join</span>(<span class="params">...paths: <span class="built_in">any</span>[]</span>): <span class="title">string</span></span>;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">let</span> sep: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>现在我们可以<code>/// &lt;reference&gt;</code> <code>node.d.ts</code>并且使用<code>import url = require(&quot;url&quot;);</code>或<code>import * as URL from &quot;url&quot;</code>加载模块。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// &lt;reference path="node.d.ts"/&gt;</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> URL <span class="keyword">from</span> <span class="string">"url"</span>;</span><br><span class="line"><span class="keyword">let</span> myUrl = URL.parse(<span class="string">"http://www.typescriptlang.org"</span>);</span><br></pre></td></tr></table></figure>

<p>假如你不想在使用一个新模块之前花时间去编写声明，你可以采用声明的简写形式以便能够快速使用它。</p>
<p>declarations.d.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> "hot-new-<span class="keyword">module</span>";</span><br></pre></td></tr></table></figure>

<p>简写模块里所有导出的类型将是<code>any</code>。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> x, &#123;y&#125; <span class="keyword">from</span> <span class="string">"hot-new-module"</span>;</span><br><span class="line">x(y);</span><br></pre></td></tr></table></figure>

<p>模块声明通配符：</p>
<p>某些模块加载器如<a href="https://github.com/systemjs/systemjs/blob/master/docs/overview.md#plugin-syntax" target="_blank" rel="noopener">SystemJS</a> 和 <a href="https://github.com/amdjs/amdjs-api/blob/master/LoaderPlugins.md" target="_blank" rel="noopener">AMD</a>支持导入非JavaScript内容。 它们通常会使用一个前缀或后缀来表示特殊的加载语法。 模块声明通配符可以用来表示这些情况。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> "*!text" &#123;</span><br><span class="line">    <span class="keyword">const</span> content: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">default</span> content;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Some do it the other way around.</span></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> "json!*" &#123;</span><br><span class="line">    <span class="keyword">const</span> value: <span class="built_in">any</span>;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">default</span> value;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>现在你可以就导入匹配<code>&quot;*!text&quot;</code>或<code>&quot;json!*&quot;</code>的内容了。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> fileContent <span class="keyword">from</span> <span class="string">"./xyz.txt!text"</span>;</span><br><span class="line"><span class="keyword">import</span> data <span class="keyword">from</span> <span class="string">"json!http://example.com/data.json"</span>;</span><br><span class="line"><span class="built_in">console</span>.log(data, fileContent);</span><br></pre></td></tr></table></figure>

<h4 id="UMD模块"><a href="#UMD模块" class="headerlink" title="UMD模块"></a>UMD模块</h4><p>有些模块被设计成兼容多个模块加载器，或者不使用模块加载器（全局变量）。 它们以 <a href="https://github.com/umdjs/umd" target="_blank" rel="noopener">UMD</a>模块为代表。 这些库可以通过导入的形式或全局变量的形式访问。 例如：</p>
<p>math-lib.d.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">isPrime</span>(<span class="params">x: <span class="built_in">number</span></span>): <span class="title">boolean</span></span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">as</span> <span class="keyword">namespace</span> mathLib;</span><br></pre></td></tr></table></figure>

<p>之后，这个库可以在某个模块里通过导入来使用：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; isPrime &#125; <span class="keyword">from</span> <span class="string">"math-lib"</span>;</span><br><span class="line">isPrime(<span class="number">2</span>);</span><br><span class="line">mathLib.isPrime(<span class="number">2</span>); <span class="comment">// 错误: 不能在模块内使用全局定义。</span></span><br></pre></td></tr></table></figure>

<p>它同样可以通过全局变量的形式使用，但只能在某个脚本（指不带有模块导入或导出的脚本文件）里。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mathLib.isPrime(<span class="number">2</span>);</span><br></pre></td></tr></table></figure>

<h3 id="创建模块结构指导"><a href="#创建模块结构指导" class="headerlink" title="创建模块结构指导"></a>创建模块结构指导</h3><p>尽可能地在顶层导出：</p>
<p>用户应该更容易地使用你模块导出的内容。 嵌套层次过多会变得难以处理，因此仔细考虑一下如何组织你的代码。</p>
<p>从你的模块中导出一个命名空间就是一个增加嵌套的例子。 虽然命名空间有时候有它们的用处，在使用模块的时候它们额外地增加了一层。 这对用户来说是很不便的并且通常是多余的。</p>
<p>导出类的静态方法也有同样的问题 - 这个类本身就增加了一层嵌套。 除非它能方便表述或便于清晰使用，否则请考虑直接导出一个辅助方法。</p>
<p>如果仅导出单个 <code>class</code> 或 <code>function</code>，使用 <code>export default</code></p>
<p>就像“在顶层上导出”帮助减少用户使用的难度，一个默认的导出也能起到这个效果。 如果一个模块就是为了导出特定的内容，那么你应该考虑使用一个默认导出。 这会令模块的导入和使用变得些许简单。 比如：</p>
<p>MyClass.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">class</span> SomeType &#123;</span><br><span class="line">  <span class="keyword">constructor</span>(<span class="params"></span>) &#123; ... &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>MyFunc.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="function"><span class="keyword">function</span> <span class="title">getThing</span>(<span class="params"></span>) </span>&#123; <span class="keyword">return</span> <span class="string">'thing'</span>; &#125;</span><br></pre></td></tr></table></figure>

<p>Consumer.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> t <span class="keyword">from</span> <span class="string">"./MyClass"</span>;</span><br><span class="line"><span class="keyword">import</span> f <span class="keyword">from</span> <span class="string">"./MyFunc"</span>;</span><br><span class="line"><span class="keyword">let</span> x = <span class="keyword">new</span> t();</span><br><span class="line"><span class="built_in">console</span>.log(f());</span><br></pre></td></tr></table></figure>

<p>对用户来说这是最理想的。他们可以随意命名导入模块的类型（本例为<code>t</code>）并且不需要多余的（.）来找到相关对象。</p>
<p>如果要导出多个对象，把它们放在顶层里导出：</p>
<p>MyThings.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> SomeType &#123; <span class="comment">/* ... */</span> &#125;</span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">someFunc</span>(<span class="params"></span>) </span>&#123; <span class="comment">/* ... */</span> &#125;</span><br></pre></td></tr></table></figure>

<p>相反地，当导入的时候：</p>
<p>明确地列出导入的名字：</p>
<p>Consumer.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; SomeType, someFunc &#125; <span class="keyword">from</span> <span class="string">"./MyThings"</span>;</span><br><span class="line"><span class="keyword">let</span> x = <span class="keyword">new</span> SomeType();</span><br><span class="line"><span class="keyword">let</span> y = someFunc();</span><br></pre></td></tr></table></figure>

<p>使用命名空间导入模式当你要导出大量内容的时候：</p>
<p>MyLargeModule.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> Dog &#123; ... &#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> Cat &#123; ... &#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> Tree &#123; ... &#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> Flower &#123; ... &#125;</span><br></pre></td></tr></table></figure>

<p>Consumer.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> myLargeModule <span class="keyword">from</span> <span class="string">"./MyLargeModule.ts"</span>;</span><br><span class="line"><span class="keyword">let</span> x = <span class="keyword">new</span> myLargeModule.Dog();</span><br></pre></td></tr></table></figure>

<h3 id="使用重新导出进行扩展"><a href="#使用重新导出进行扩展" class="headerlink" title="使用重新导出进行扩展"></a>使用重新导出进行扩展</h3><p>你可能经常需要去扩展一个模块的功能。 JS里常用的一个模式是JQuery那样去扩展原对象。 如我们之前提到的，模块不会像全局命名空间对象那样去 <em>合并</em>。 推荐的方案是 <em>不要</em>去改变原来的对象，而是导出一个新的实体来提供新的功能。</p>
<p>假设<code>Calculator.ts</code>模块里定义了一个简单的计算器实现。 这个模块同样提供了一个辅助函数来测试计算器的功能，通过传入一系列输入的字符串并在最后给出结果。</p>
<p>Calculator.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> Calculator &#123;</span><br><span class="line">    <span class="keyword">private</span> current = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">private</span> memory = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">private</span> operator: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> processDigit(digit: <span class="built_in">string</span>, currentValue: <span class="built_in">number</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (digit &gt;= <span class="string">"0"</span> &amp;&amp; digit &lt;= <span class="string">"9"</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> currentValue * <span class="number">10</span> + (digit.charCodeAt(<span class="number">0</span>) - <span class="string">"0"</span>.charCodeAt(<span class="number">0</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> processOperator(operator: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> ([<span class="string">"+"</span>, <span class="string">"-"</span>, <span class="string">"*"</span>, <span class="string">"/"</span>].indexOf(operator) &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> operator;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> evaluateOperator(operator: <span class="built_in">string</span>, left: <span class="built_in">number</span>, right: <span class="built_in">number</span>): <span class="built_in">number</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> (<span class="keyword">this</span>.operator) &#123;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">"+"</span>: <span class="keyword">return</span> left + right;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">"-"</span>: <span class="keyword">return</span> left - right;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">"*"</span>: <span class="keyword">return</span> left * right;</span><br><span class="line">            <span class="keyword">case</span> <span class="string">"/"</span>: <span class="keyword">return</span> left / right;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> evaluate() &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">this</span>.operator) &#123;</span><br><span class="line">            <span class="keyword">this</span>.memory = <span class="keyword">this</span>.evaluateOperator(<span class="keyword">this</span>.operator, <span class="keyword">this</span>.memory, <span class="keyword">this</span>.current);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.memory = <span class="keyword">this</span>.current;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.current = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> handleChar(char: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (char === <span class="string">"="</span>) &#123;</span><br><span class="line">            <span class="keyword">this</span>.evaluate();</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">let</span> value = <span class="keyword">this</span>.processDigit(char, <span class="keyword">this</span>.current);</span><br><span class="line">            <span class="keyword">if</span> (value !== <span class="literal">undefined</span>) &#123;</span><br><span class="line">                <span class="keyword">this</span>.current = value;</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">let</span> value = <span class="keyword">this</span>.processOperator(char);</span><br><span class="line">                <span class="keyword">if</span> (value !== <span class="literal">undefined</span>) &#123;</span><br><span class="line">                    <span class="keyword">this</span>.evaluate();</span><br><span class="line">                    <span class="keyword">this</span>.operator = value;</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">`Unsupported input: '<span class="subst">$&#123;char&#125;</span>'`</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> getResult() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.memory;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">test</span>(<span class="params">c: Calculator, input: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; input.length; i++) &#123;</span><br><span class="line">        c.handleChar(input[i]);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">`result of '<span class="subst">$&#123;input&#125;</span>' is '<span class="subst">$&#123;c.getResult()&#125;</span>'`</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面使用导出的<code>test</code>函数来测试计算器。</p>
<p>TestCalculator.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; Calculator, test &#125; <span class="keyword">from</span> <span class="string">"./Calculator"</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> c = <span class="keyword">new</span> Calculator();</span><br><span class="line">test(c, <span class="string">"1+2*33/11="</span>); <span class="comment">// prints 9</span></span><br></pre></td></tr></table></figure>

<p>现在扩展它，添加支持输入其它进制（十进制以外），让我们来创建<code>ProgrammerCalculator.ts</code>。</p>
<p>ProgrammerCalculator.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; Calculator &#125; <span class="keyword">from</span> <span class="string">"./Calculator"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> ProgrammerCalculator <span class="keyword">extends</span> Calculator &#123;</span><br><span class="line">    <span class="keyword">static</span> digits = [<span class="string">"0"</span>, <span class="string">"1"</span>, <span class="string">"2"</span>, <span class="string">"3"</span>, <span class="string">"4"</span>, <span class="string">"5"</span>, <span class="string">"6"</span>, <span class="string">"7"</span>, <span class="string">"8"</span>, <span class="string">"9"</span>, <span class="string">"A"</span>, <span class="string">"B"</span>, <span class="string">"C"</span>, <span class="string">"D"</span>, <span class="string">"E"</span>, <span class="string">"F"</span>];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params"><span class="keyword">public</span> base: <span class="built_in">number</span></span>) &#123;</span><br><span class="line">        <span class="keyword">super</span>();</span><br><span class="line">        <span class="keyword">const</span> maxBase = ProgrammerCalculator.digits.length;</span><br><span class="line">        <span class="keyword">if</span> (base &lt;= <span class="number">0</span> || base &gt; maxBase) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">`base has to be within 0 to <span class="subst">$&#123;maxBase&#125;</span> inclusive.`</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> processDigit(digit: <span class="built_in">string</span>, currentValue: <span class="built_in">number</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (ProgrammerCalculator.digits.indexOf(digit) &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> currentValue * <span class="keyword">this</span>.base + ProgrammerCalculator.digits.indexOf(digit);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Export the new extended calculator as Calculator</span></span><br><span class="line"><span class="keyword">export</span> &#123; ProgrammerCalculator <span class="keyword">as</span> Calculator &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Also, export the helper function</span></span><br><span class="line"><span class="keyword">export</span> &#123; test &#125; <span class="keyword">from</span> <span class="string">"./Calculator"</span>;</span><br></pre></td></tr></table></figure>

<p>新的<code>ProgrammerCalculator</code>模块导出的API与原先的<code>Calculator</code>模块很相似，但却没有改变原模块里的对象。 下面是测试ProgrammerCalculator类的代码：</p>
<p>TestProgrammerCalculator.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; Calculator, test &#125; <span class="keyword">from</span> <span class="string">"./ProgrammerCalculator"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> c = <span class="keyword">new</span> Calculator(<span class="number">2</span>);</span><br><span class="line">test(c, <span class="string">"001+010="</span>); <span class="comment">// prints 3</span></span><br></pre></td></tr></table></figure>

<p>模块里不要使用命名空间：</p>
<p>当初次进入基于模块的开发模式时，可能总会控制不住要将导出包裹在一个命名空间里。 模块具有其自己的作用域，并且只有导出的声明才会在模块外部可见。 记住这点，命名空间在使用模块时几乎没什么价值。</p>
<p>在组织方面，命名空间对于在全局作用域内对逻辑上相关的对象和类型进行分组是很便利的。 例如，在C#里，你会从 <code>System.Collections</code>里找到所有集合的类型。 通过将类型有层次地组织在命名空间里，可以方便用户找到与使用那些类型。 然而，模块本身已经存在于文件系统之中，这是必须的。 我们必须通过路径和文件名找到它们，这已经提供了一种逻辑上的组织形式。 我们可以创建 <code>/collections/generic/</code>文件夹，把相应模块放在这里面。</p>
<p>命名空间对解决全局作用域里命名冲突来说是很重要的。 比如，你可以有一个 <code>My.Application.Customer.AddForm</code>和<code>My.Application.Order.AddForm</code> – 两个类型的名字相同，但命名空间不同。 然而，这对于模块来说却不是一个问题。 在一个模块里，没有理由两个对象拥有同一个名字。 从模块的使用角度来说，使用者会挑出他们用来引用模块的名字，所以也没有理由发生重名的情况。</p>
<blockquote>
<p>更多关于模块和命名空间的资料查看<a href="https://www.tslang.cn/docs/handbook/namespaces-and-modules.html" target="_blank" rel="noopener">命名空间和模块</a></p>
</blockquote>
<h3 id="危险信号"><a href="#危险信号" class="headerlink" title="危险信号"></a>危险信号</h3><p>以下均为模块结构上的危险信号。重新检查以确保你没有在对模块使用命名空间：</p>
<ul>
<li>文件的顶层声明是<code>export namespace Foo { ... }</code> （删除<code>Foo</code>并把所有内容向上层移动一层）</li>
<li>文件只有一个<code>export class</code>或<code>export function</code> （考虑使用<code>export default</code>）</li>
<li>多个文件的顶层具有同样的<code>export namespace Foo {</code> （不要以为这些会合并到一个<code>Foo</code>中！）</li>
</ul>
<h2 id="命名空间"><a href="#命名空间" class="headerlink" title="命名空间"></a>命名空间</h2><p>这篇文章描述了如何在TypeScript里使用命名空间（之前叫做“内部模块”）来组织你的代码。 就像我们在术语说明里提到的那样，“内部模块”现在叫做“命名空间”。 另外，任何使用 <code>module</code>关键字来声明一个内部模块的地方都应该使用<code>namespace</code>关键字来替换。 这就避免了让新的使用者被相似的名称所迷惑。</p>
<p>我们先来写一段程序并将在整篇文章中都使用这个例子。 我们定义几个简单的字符串验证器，假设你会使用它们来验证表单里的用户输入或验证外部数据。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> StringValidator &#123;</span><br><span class="line">    isAcceptable(s: <span class="built_in">string</span>): <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> lettersRegexp = <span class="regexp">/^[A-Za-z]+$/</span>;</span><br><span class="line"><span class="keyword">let</span> numberRegexp = <span class="regexp">/^[0-9]+$/</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> LettersOnlyValidator <span class="keyword">implements</span> StringValidator &#123;</span><br><span class="line">    isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> lettersRegexp.test(s);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> ZipCodeValidator <span class="keyword">implements</span> StringValidator &#123;</span><br><span class="line">    isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> s.length === <span class="number">5</span> &amp;&amp; numberRegexp.test(s);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Some samples to try</span></span><br><span class="line"><span class="keyword">let</span> strings = [<span class="string">"Hello"</span>, <span class="string">"98052"</span>, <span class="string">"101"</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// Validators to use</span></span><br><span class="line"><span class="keyword">let</span> validators: &#123; [s: <span class="built_in">string</span>]: StringValidator; &#125; = &#123;&#125;;</span><br><span class="line">validators[<span class="string">"ZIP code"</span>] = <span class="keyword">new</span> ZipCodeValidator();</span><br><span class="line">validators[<span class="string">"Letters only"</span>] = <span class="keyword">new</span> LettersOnlyValidator();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Show whether each string passed each validator</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> s of strings) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> name <span class="keyword">in</span> validators) &#123;</span><br><span class="line">        <span class="keyword">let</span> isMatch = validators[name].isAcceptable(s);</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">`'<span class="subst">$&#123; s &#125;</span>' <span class="subst">$&#123; isMatch ? <span class="string">"matches"</span> : <span class="string">"does not match"</span> &#125;</span> '<span class="subst">$&#123; name &#125;</span>'.`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="命名空间-1"><a href="#命名空间-1" class="headerlink" title="命名空间"></a>命名空间</h3><p>随着更多验证器的加入，我们需要一种手段来组织代码，以便于在记录它们类型的同时还不用担心与其它对象产生命名冲突。 因此，我们把验证器包裹到一个命名空间内，而不是把它们放在全局命名空间下。</p>
<p>下面的例子里，把所有与验证器相关的类型都放到一个叫做<code>Validation</code>的命名空间里。 因为我们想让这些接口和类在命名空间之外也是可访问的，所以需要使用 <code>export</code>。 相反的，变量 <code>lettersRegexp</code>和<code>numberRegexp</code>是实现的细节，不需要导出，因此它们在命名空间外是不能访问的。 在文件末尾的测试代码里，由于是在命名空间之外访问，因此需要限定类型的名称，比如 <code>Validation.LettersOnlyValidator</code>。</p>
<p>使用命名空间的验证器：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> Validation &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">interface</span> StringValidator &#123;</span><br><span class="line">        isAcceptable(s: <span class="built_in">string</span>): <span class="built_in">boolean</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> lettersRegexp = <span class="regexp">/^[A-Za-z]+$/</span>;</span><br><span class="line">    <span class="keyword">const</span> numberRegexp = <span class="regexp">/^[0-9]+$/</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">class</span> LettersOnlyValidator <span class="keyword">implements</span> StringValidator &#123;</span><br><span class="line">        isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> lettersRegexp.test(s);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">class</span> ZipCodeValidator <span class="keyword">implements</span> StringValidator &#123;</span><br><span class="line">        isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> s.length === <span class="number">5</span> &amp;&amp; numberRegexp.test(s);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Some samples to try</span></span><br><span class="line"><span class="keyword">let</span> strings = [<span class="string">"Hello"</span>, <span class="string">"98052"</span>, <span class="string">"101"</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// Validators to use</span></span><br><span class="line"><span class="keyword">let</span> validators: &#123; [s: <span class="built_in">string</span>]: Validation.StringValidator; &#125; = &#123;&#125;;</span><br><span class="line">validators[<span class="string">"ZIP code"</span>] = <span class="keyword">new</span> Validation.ZipCodeValidator();</span><br><span class="line">validators[<span class="string">"Letters only"</span>] = <span class="keyword">new</span> Validation.LettersOnlyValidator();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Show whether each string passed each validator</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> s of strings) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> name <span class="keyword">in</span> validators) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">`"<span class="subst">$&#123; s &#125;</span>" - <span class="subst">$&#123; validators[name].isAcceptable(s) ? <span class="string">"matches"</span> : <span class="string">"does not match"</span> &#125;</span> <span class="subst">$&#123; name &#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="分离到多文件"><a href="#分离到多文件" class="headerlink" title="分离到多文件"></a>分离到多文件</h3><p>当应用变得越来越大时，我们需要将代码分离到不同的文件中以便于维护。</p>
<p>多文件中的命名空间：</p>
<p>现在，我们把<code>Validation</code>命名空间分割成多个文件。 尽管是不同的文件，它们仍是同一个命名空间，并且在使用的时候就如同它们在一个文件中定义的一样。 因为不同文件之间存在依赖关系，所以我们加入了引用标签来告诉编译器文件之间的关联。 我们的测试代码保持不变。</p>
<p>Validation.ts：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> Validation &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">interface</span> StringValidator &#123;</span><br><span class="line">        isAcceptable(s: <span class="built_in">string</span>): <span class="built_in">boolean</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>LettersOnlyValidator.ts：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// &lt;reference path="Validation.ts" /&gt;</span></span><br><span class="line"><span class="keyword">namespace</span> Validation &#123;</span><br><span class="line">    <span class="keyword">const</span> lettersRegexp = <span class="regexp">/^[A-Za-z]+$/</span>;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">class</span> LettersOnlyValidator <span class="keyword">implements</span> StringValidator &#123;</span><br><span class="line">        isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> lettersRegexp.test(s);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>ZipCodeValidator.ts：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// &lt;reference path="Validation.ts" /&gt;</span></span><br><span class="line"><span class="keyword">namespace</span> Validation &#123;</span><br><span class="line">    <span class="keyword">const</span> numberRegexp = <span class="regexp">/^[0-9]+$/</span>;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">class</span> ZipCodeValidator <span class="keyword">implements</span> StringValidator &#123;</span><br><span class="line">        isAcceptable(s: <span class="built_in">string</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> s.length === <span class="number">5</span> &amp;&amp; numberRegexp.test(s);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Test.ts：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// &lt;reference path="Validation.ts" /&gt;</span></span><br><span class="line"><span class="comment">/// &lt;reference path="LettersOnlyValidator.ts" /&gt;</span></span><br><span class="line"><span class="comment">/// &lt;reference path="ZipCodeValidator.ts" /&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Some samples to try</span></span><br><span class="line"><span class="keyword">let</span> strings = [<span class="string">"Hello"</span>, <span class="string">"98052"</span>, <span class="string">"101"</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// Validators to use</span></span><br><span class="line"><span class="keyword">let</span> validators: &#123; [s: <span class="built_in">string</span>]: Validation.StringValidator; &#125; = &#123;&#125;;</span><br><span class="line">validators[<span class="string">"ZIP code"</span>] = <span class="keyword">new</span> Validation.ZipCodeValidator();</span><br><span class="line">validators[<span class="string">"Letters only"</span>] = <span class="keyword">new</span> Validation.LettersOnlyValidator();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Show whether each string passed each validator</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> s of strings) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> name <span class="keyword">in</span> validators) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">`"<span class="subst">$&#123; s &#125;</span>" - <span class="subst">$&#123; validators[name].isAcceptable(s) ? <span class="string">"matches"</span> : <span class="string">"does not match"</span> &#125;</span> <span class="subst">$&#123; name &#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当涉及到多文件时，我们必须确保所有编译后的代码都被加载了。 我们有两种方式。</p>
<p>第一种方式，把所有的输入文件编译为一个输出文件，需要使用<code>--outFile</code>标记：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tsc --outFile sample.js Test.ts</span><br></pre></td></tr></table></figure>

<p>编译器会根据源码里的引用标签自动地对输出进行排序。你也可以单独地指定每个文件。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts</span><br></pre></td></tr></table></figure>

<p>第二种方式，我们可以编译每一个文件（默认方式），那么每个源文件都会对应生成一个JavaScript文件。 然后，在页面上通过 <code>&lt;script&gt;</code>标签把所有生成的JavaScript文件按正确的顺序引进来，比如：</p>
<p>MyTestPage.html (excerpt)：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"Validation.js"</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> /&gt;</span></span><br><span class="line"><span class="handlebars"><span class="xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"LettersOnlyValidator.js"</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> /&gt;</span></span></span></span><br><span class="line"><span class="handlebars"><span class="xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"ZipCodeValidator.js"</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> /&gt;</span></span></span></span><br><span class="line"><span class="handlebars"><span class="xml"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"Test.js"</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> /&gt;</span></span></span></span><br></pre></td></tr></table></figure>

<h3 id="别名"><a href="#别名" class="headerlink" title="别名"></a>别名</h3><p>另一种简化命名空间操作的方法是使用<code>import q = x.y.z</code>给常用的对象起一个短的名字。 不要与用来加载模块的 <code>import x = require(&#39;name&#39;)</code>语法弄混了，这里的语法是为指定的符号创建一个别名。 你可以用这种方法为任意标识符创建别名，也包括导入的模块中的对象。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> Shapes &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">namespace</span> Polygons &#123;</span><br><span class="line">        <span class="keyword">export</span> <span class="keyword">class</span> Triangle &#123; &#125;</span><br><span class="line">        <span class="keyword">export</span> <span class="keyword">class</span> Square &#123; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> polygons = Shapes.Polygons;</span><br><span class="line"><span class="keyword">let</span> sq = <span class="keyword">new</span> polygons.Square(); <span class="comment">// Same as "new Shapes.Polygons.Square()"</span></span><br></pre></td></tr></table></figure>

<p>注意，我们并没有使用<code>require</code>关键字，而是直接使用导入符号的限定名赋值。 这与使用 <code>var</code>相似，但它还适用于类型和导入的具有命名空间含义的符号。 重要的是，对于值来讲， <code>import</code>会生成与原始符号不同的引用，所以改变别名的<code>var</code>值并不会影响原始变量的值。</p>
<h3 id="使用其它的JavaScript库"><a href="#使用其它的JavaScript库" class="headerlink" title="使用其它的JavaScript库"></a>使用其它的JavaScript库</h3><p>为了描述不是用TypeScript编写的类库的类型，我们需要声明类库导出的API。 由于大部分程序库只提供少数的顶级对象，命名空间是用来表示它们的一个好办法。</p>
<p>我们称其为声明是因为它不是外部程序的具体实现。 我们通常在 <code>.d.ts</code>里写这些声明。 如果你熟悉C/C++，你可以把它们当做 <code>.h</code>文件。 让我们看一些例子。</p>
<p>外部命名空间：</p>
<p>流行的程序库D3在全局对象<code>d3</code>里定义它的功能。 因为这个库通过一个 <code>&lt;script&gt;</code>标签加载（不是通过模块加载器），它的声明文件使用内部模块来定义它的类型。 为了让TypeScript编译器识别它的类型，我们使用外部命名空间声明。 比如，我们可以像下面这样写：</p>
<p>D3.d.ts (部分摘录)：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">namespace</span> D3 &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">interface</span> Selectors &#123;</span><br><span class="line">        select: &#123;</span><br><span class="line">            (selector: <span class="built_in">string</span>): Selection;</span><br><span class="line">            (element: EventTarget): Selection;</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">interface</span> Event &#123;</span><br><span class="line">        x: <span class="built_in">number</span>;</span><br><span class="line">        y: <span class="built_in">number</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">interface</span> Base <span class="keyword">extends</span> Selectors &#123;</span><br><span class="line">        event: Event;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">var</span> d3: D3.Base;</span><br></pre></td></tr></table></figure>

<h2 id="命名空间与模块"><a href="#命名空间与模块" class="headerlink" title="命名空间与模块"></a>命名空间与模块</h2><p>这篇文章将概括介绍在TypeScript里使用模块与命名空间来组织代码的方法。 我们也会谈及命名空间和模块的高级使用场景，和在使用它们的过程中常见的陷阱。</p>
<p>查看<a href="https://www.tslang.cn/docs/handbook/modules.html" target="_blank" rel="noopener">模块</a>章节了解关于模块的更多信息。 查看 <a href="https://www.tslang.cn/docs/handbook/namespaces.html" target="_blank" rel="noopener">命名空间</a>章节了解关于命名空间的更多信息。</p>
<p>使用命名空间：</p>
<p>命名空间是位于全局命名空间下的一个普通的带有名字的JavaScript对象。 这令命名空间十分容易使用。 它们可以在多文件中同时使用，并通过 <code>--outFile</code>结合在一起。 命名空间是帮你组织Web应用不错的方式，你可以把所有依赖都放在HTML页面的 <code>&lt;script&gt;</code>标签里。</p>
<p>但就像其它的全局命名空间污染一样，它很难去识别组件之间的依赖关系，尤其是在大型的应用中。</p>
<p>使用模块：</p>
<p>像命名空间一样，模块可以包含代码和声明。 不同的是模块可以 <em>声明</em>它的依赖。</p>
<p>模块会把依赖添加到模块加载器上（例如CommonJs / Require.js）。 对于小型的JS应用来说可能没必要，但是对于大型应用，这一点点的花费会带来长久的模块化和可维护性上的便利。 模块也提供了更好的代码重用，更强的封闭性以及更好的使用工具进行优化。</p>
<p>对于Node.js应用来说，模块是默认并推荐的组织代码的方式。</p>
<p>从ECMAScript 2015开始，模块成为了语言内置的部分，应该会被所有正常的解释引擎所支持。 因此，对于新项目来说推荐使用模块做为组织代码的方式。</p>
<h3 id="命名空间和模块的陷阱"><a href="#命名空间和模块的陷阱" class="headerlink" title="命名空间和模块的陷阱"></a>命名空间和模块的陷阱</h3><p>这部分我们会描述常见的命名空间和模块的使用陷阱和如何去避免它们。</p>
<p>对模块使用<code>/// &lt;reference&gt;</code>：</p>
<p>一个常见的错误是使用<code>/// &lt;reference&gt;</code>引用模块文件，应该使用<code>import</code>。 要理解这之间的区别，我们首先应该弄清编译器是如何根据 <code>import</code>路径（例如，<code>import x from &quot;...&quot;;</code>或<code>import x = require(&quot;...&quot;)</code>里面的<code>...</code>，等等）来定位模块的类型信息的。</p>
<p>编译器首先尝试去查找相应路径下的<code>.ts</code>，<code>.tsx</code>再或者<code>.d.ts</code>。 如果这些文件都找不到，编译器会查找 <em>外部模块声明</em>。 回想一下，它们是在 <code>.d.ts</code>文件里声明的。</p>
<ul>
<li><code>myModules.d.ts</code></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// In a .d.ts file or .ts file that is not a module:</span></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> "SomeModule" &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>): <span class="title">string</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li><code>myOtherModule.ts</code></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// &lt;reference path="myModules.d.ts" /&gt;</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> m <span class="keyword">from</span> <span class="string">"SomeModule"</span>;</span><br></pre></td></tr></table></figure>

<p>这里的引用标签指定了外来模块的位置。 这就是一些TypeScript例子中引用 <code>node.d.ts</code>的方法。</p>
<p>不必要的命名空间：</p>
<p>如果你想把命名空间转换为模块，它可能会像下面这个文件一件：</p>
<ul>
<li><code>shapes.ts</code></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">namespace</span> Shapes &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">class</span> Triangle &#123; <span class="comment">/* ... */</span> &#125;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">class</span> Square &#123; <span class="comment">/* ... */</span> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>顶层的模块<code>Shapes</code>包裹了<code>Triangle</code>和<code>Square</code>。 对于使用它的人来说这是令人迷惑和讨厌的：</p>
<ul>
<li><code>shapeConsumer.ts</code></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> shapes <span class="keyword">from</span> <span class="string">"./shapes"</span>;</span><br><span class="line"><span class="keyword">let</span> t = <span class="keyword">new</span> shapes.Shapes.Triangle(); <span class="comment">// shapes.Shapes?</span></span><br></pre></td></tr></table></figure>

<p>TypeScript里模块的一个特点是不同的模块永远也不会在相同的作用域内使用相同的名字。 因为使用模块的人会为它们命名，所以完全没有必要把导出的符号包裹在一个命名空间里。</p>
<p>再次重申，不应该对模块使用命名空间，使用命名空间是为了提供逻辑分组和避免命名冲突。 模块文件本身已经是一个逻辑分组，并且它的名字是由导入这个模块的代码指定，所以没有必要为导出的对象增加额外的模块层。</p>
<p>下面是改进的例子：</p>
<ul>
<li><code>shapes.ts</code></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> Triangle &#123; <span class="comment">/* ... */</span> &#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> Square &#123; <span class="comment">/* ... */</span> &#125;</span><br></pre></td></tr></table></figure>

<ul>
<li><code>shapeConsumer.ts</code></li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> shapes <span class="keyword">from</span> <span class="string">"./shapes"</span>;</span><br><span class="line"><span class="keyword">let</span> t = <span class="keyword">new</span> shapes.Triangle();</span><br></pre></td></tr></table></figure>

<h3 id="模块的取舍"><a href="#模块的取舍" class="headerlink" title="模块的取舍"></a>模块的取舍</h3><p>就像每个JS文件对应一个模块一样，TypeScript里模块文件与生成的JS文件也是一一对应的。 这会产生一种影响，根据你指定的目标模块系统的不同，你可能无法连接多个模块源文件。 例如当目标模块系统为 <code>commonjs</code>或<code>umd</code>时，无法使用<code>outFile</code>选项，但是在TypeScript 1.8以上的版本<a href="https://www.tslang.cn/docs/release-notes/typescript-1.8.html#concatenate-amd-and-system-modules-with---outfile" target="_blank" rel="noopener">能够</a>使用<code>outFile</code>当目标为<code>amd</code>或<code>system</code>。</p>
<h2 id="模块解析"><a href="#模块解析" class="headerlink" title="模块解析"></a>模块解析</h2><p>模块解析是指编译器在查找导入模块内容时所遵循的流程。假设有一个导入语句 <code>import { a } from &quot;moduleA&quot;</code>; 为了去检查任何对 <code>a</code>的使用，编译器需要准确的知道它表示什么，并且需要检查它的定义<code>moduleA</code>。</p>
<p>这时候，编译器会有个疑问“<code>moduleA</code>的结构是怎样的？” 这听上去很简单，但 <code>moduleA</code>可能在你写的某个<code>.ts</code>/<code>.tsx</code>文件里或者在你的代码所依赖的<code>.d.ts</code>里。</p>
<p>首先，编译器会尝试定位表示导入模块的文件。 编译器会遵循以下二种策略之一： <a href="https://www.tslang.cn/docs/handbook/module-resolution.html#classic" target="_blank" rel="noopener">Classic</a>或<a href="https://www.tslang.cn/docs/handbook/module-resolution.html#node" target="_blank" rel="noopener">Node</a>。 这些策略会告诉编译器到 <em>哪里</em>去查找<code>moduleA</code>。如果上面的解析失败了并且模块名是非相对的（且是在<code>&quot;moduleA&quot;</code>的情况下），编译器会尝试定位一个<a href="https://www.tslang.cn/docs/handbook/modules.html#ambient-modules" target="_blank" rel="noopener">外部模块声明</a>。 我们接下来会讲到非相对导入。最后，如果编译器还是不能解析这个模块，它会记录一个错误。 在这种情况下，错误可能为 <code>error TS2307: Cannot find module &#39;moduleA&#39;.</code></p>
<p>相对 vs. 非相对模块导入：</p>
<p>根据模块引用是相对的还是非相对的，模块导入会以不同的方式解析。</p>
<p>相对导入是以<code>/</code>，<code>./</code>或<code>../</code>开头的。 下面是一些例子：</p>
<ul>
<li><code>import Entry from &quot;./components/Entry&quot;;</code></li>
<li><code>import { DefaultHeaders } from &quot;../constants/http&quot;;</code></li>
<li><code>import &quot;/mod&quot;;</code></li>
</ul>
<p>所有其它形式的导入被当作<em>非相对</em>的。 下面是一些例子：</p>
<ul>
<li><code>import * as $ from &quot;jQuery&quot;;</code></li>
<li><code>import { Component } from &quot;@angular/core&quot;;</code></li>
</ul>
<p>相对导入在解析时是相对于导入它的文件，并且<em>不能</em>解析为一个外部模块声明。 你应该为你自己写的模块使用相对导入，这样能确保它们在运行时的相对位置。</p>
<p>非相对模块的导入可以相对于<code>baseUrl</code>或通过下文会讲到的路径映射来进行解析。 它们还可以被解析成 <a href="https://www.tslang.cn/docs/handbook/modules.html#ambient-modules" target="_blank" rel="noopener">外部模块声明</a>。 使用非相对路径来导入你的外部依赖。</p>
<h3 id="模块解析策略"><a href="#模块解析策略" class="headerlink" title="模块解析策略"></a>模块解析策略</h3><p>共有两种可用的模块解析策略：<a href="https://www.tslang.cn/docs/handbook/module-resolution.html#node" target="_blank" rel="noopener">Node</a>和<a href="https://www.tslang.cn/docs/handbook/module-resolution.html#classic" target="_blank" rel="noopener">Classic</a>。 你可以使用 <code>--moduleResolution</code>标记来指定使用哪种模块解析策略。若未指定，那么在使用了 <code>--module AMD | System | ES2015</code>时的默认值为<a href="https://www.tslang.cn/docs/handbook/module-resolution.html#classic" target="_blank" rel="noopener">Classic</a>，其它情况时则为<a href="https://www.tslang.cn/docs/handbook/module-resolution.html#node" target="_blank" rel="noopener">Node</a>。</p>
<p>Classic：</p>
<p>这种策略在以前是TypeScript默认的解析策略。 现在，它存在的理由主要是为了向后兼容。</p>
<p>相对导入的模块是相对于导入它的文件进行解析的。 因此 <code>/root/src/folder/A.ts</code>文件里的<code>import { b } from &quot;./moduleB&quot;</code>会使用下面的查找流程：</p>
<ol>
<li><code>/root/src/folder/moduleB.ts</code></li>
<li><code>/root/src/folder/moduleB.d.ts</code></li>
</ol>
<p>对于非相对模块的导入，编译器则会从包含导入文件的目录开始依次向上级目录遍历，尝试定位匹配的声明文件。</p>
<p>比如：有一个对<code>moduleB</code>的非相对导入<code>import { b } from &quot;moduleB&quot;</code>，它是在<code>/root/src/folder/A.ts</code>文件里，会以如下的方式来定位<code>&quot;moduleB&quot;</code>：</p>
<ol>
<li><code>/root/src/folder/moduleB.ts</code></li>
<li><code>/root/src/folder/moduleB.d.ts</code></li>
<li><code>/root/src/moduleB.ts</code></li>
<li><code>/root/src/moduleB.d.ts</code></li>
<li><code>/root/moduleB.ts</code></li>
<li><code>/root/moduleB.d.ts</code></li>
<li><code>/moduleB.ts</code></li>
<li><code>/moduleB.d.ts</code></li>
</ol>
<p>Node：这个解析策略试图在运行时模仿<a href="https://nodejs.org/" target="_blank" rel="noopener">Node.js</a>模块解析机制。 完整的Node.js解析算法可以在 <a href="https://nodejs.org/api/modules.html#modules_all_together" target="_blank" rel="noopener">Node.js module documentation</a>找到。</p>
<p>Node.js如何解析模块：</p>
<p>为了理解TypeScript编译依照的解析步骤，先弄明白Node.js模块是非常重要的。 通常，在Node.js里导入是通过 <code>require</code>函数调用进行的。 Node.js会根据 <code>require</code>的是相对路径还是非相对路径做出不同的行为。</p>
<p>相对路径很简单。 例如，假设有一个文件路径为 <code>/root/src/moduleA.js</code>，包含了一个导入<code>var x = require(&quot;./moduleB&quot;);</code> Node.js以下面的顺序解析这个导入：</p>
<ol>
<li>检查<code>/root/src/moduleB.js</code>文件是否存在。</li>
<li>检查<code>/root/src/moduleB</code>目录是否包含一个<code>package.json</code>文件，且<code>package.json</code>文件指定了一个<code>&quot;main&quot;</code>模块。 在我们的例子里，如果Node.js发现文件 <code>/root/src/moduleB/package.json</code>包含了<code>{ &quot;main&quot;: &quot;lib/mainModule.js&quot; }</code>，那么Node.js会引用<code>/root/src/moduleB/lib/mainModule.js</code>。</li>
<li>检查<code>/root/src/moduleB</code>目录是否包含一个<code>index.js</code>文件。 这个文件会被隐式地当作那个文件夹下的”main”模块。</li>
</ol>
<p>你可以阅读Node.js文档了解更多详细信息：<a href="https://nodejs.org/api/modules.html#modules_file_modules" target="_blank" rel="noopener">file modules</a> 和 <a href="https://nodejs.org/api/modules.html#modules_folders_as_modules" target="_blank" rel="noopener">folder modules</a>。</p>
<p>但是，<a href="https://www.tslang.cn/docs/handbook/module-resolution.html#relative-vs-non-relative-module-imports" target="_blank" rel="noopener">非相对模块名</a>的解析是个完全不同的过程。 Node会在一个特殊的文件夹 <code>node_modules</code>里查找你的模块。 <code>node_modules</code>可能与当前文件在同一级目录下，或者在上层目录里。 Node会向上级目录遍历，查找每个 <code>node_modules</code>直到它找到要加载的模块。</p>
<p>还是用上面例子，但假设<code>/root/src/moduleA.js</code>里使用的是非相对路径导入<code>var x = require(&quot;moduleB&quot;);</code>。 Node则会以下面的顺序去解析 <code>moduleB</code>，直到有一个匹配上。</p>
<ol>
<li><code>/root/src/node_modules/moduleB.js</code></li>
<li><code>/root/src/node_modules/moduleB/package.json</code> (如果指定了<code>&quot;main&quot;</code>属性)</li>
<li><code>/root/src/node_modules/moduleB/index.js</code></li>
<li><code>/root/node_modules/moduleB.js</code></li>
<li><code>/root/node_modules/moduleB/package.json</code> (如果指定了<code>&quot;main&quot;</code>属性)</li>
<li><code>/root/node_modules/moduleB/index.js</code></li>
<li><code>/node_modules/moduleB.js</code></li>
<li><code>/node_modules/moduleB/package.json</code> (如果指定了<code>&quot;main&quot;</code>属性)</li>
<li><code>/node_modules/moduleB/index.js</code></li>
</ol>
<p>注意Node.js在步骤（4）和（7）会向上跳一级目录。</p>
<p>你可以阅读Node.js文档了解更多详细信息：<a href="https://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders" target="_blank" rel="noopener">loading modules from <code>node_modules</code></a>。</p>
<h4 id="TypeScript如何解析模块"><a href="#TypeScript如何解析模块" class="headerlink" title="TypeScript如何解析模块"></a>TypeScript如何解析模块</h4><p>TypeScript是模仿Node.js运行时的解析策略来在编译阶段定位模块定义文件。 因此，TypeScript在Node解析逻辑基础上增加了TypeScript源文件的扩展名（ <code>.ts</code>，<code>.tsx</code>和<code>.d.ts</code>）。 同时，TypeScript在 <code>package.json</code>里使用字段<code>&quot;types&quot;</code>来表示类似<code>&quot;main&quot;</code>的意义 - 编译器会使用它来找到要使用的”main”定义文件。</p>
<p>比如，有一个导入语句<code>import { b } from &quot;./moduleB&quot;</code>在<code>/root/src/moduleA.ts</code>里，会以下面的流程来定位<code>&quot;./moduleB&quot;</code>：</p>
<ol>
<li><code>/root/src/moduleB.ts</code></li>
<li><code>/root/src/moduleB.tsx</code></li>
<li><code>/root/src/moduleB.d.ts</code></li>
<li><code>/root/src/moduleB/package.json</code> (如果指定了<code>&quot;types&quot;</code>属性)</li>
<li><code>/root/src/moduleB/index.ts</code></li>
<li><code>/root/src/moduleB/index.tsx</code></li>
<li><code>/root/src/moduleB/index.d.ts</code></li>
</ol>
<p>回想一下Node.js先查找<code>moduleB.js</code>文件，然后是合适的<code>package.json</code>，再之后是<code>index.js</code>。</p>
<p>类似地，非相对的导入会遵循Node.js的解析逻辑，首先查找文件，然后是合适的文件夹。 因此 <code>/root/src/moduleA.ts</code>文件里的<code>import { b } from &quot;moduleB&quot;</code>会以下面的查找顺序解析：</p>
<ol>
<li><code>/root/src/node_modules/moduleB.ts</code></li>
<li><code>/root/src/node_modules/moduleB.tsx</code></li>
<li><code>/root/src/node_modules/moduleB.d.ts</code></li>
<li><code>/root/src/node_modules/moduleB/package.json</code> (如果指定了<code>&quot;types&quot;</code>属性)</li>
<li><code>/root/src/node_modules/moduleB/index.ts</code></li>
<li><code>/root/src/node_modules/moduleB/index.tsx</code></li>
<li><code>/root/src/node_modules/moduleB/index.d.ts</code></li>
<li><code>/root/node_modules/moduleB.ts</code></li>
<li><code>/root/node_modules/moduleB.tsx</code></li>
<li><code>/root/node_modules/moduleB.d.ts</code></li>
<li><code>/root/node_modules/moduleB/package.json</code> (如果指定了<code>&quot;types&quot;</code>属性)</li>
<li><code>/root/node_modules/moduleB/index.ts</code></li>
<li><code>/root/node_modules/moduleB/index.tsx</code></li>
<li><code>/root/node_modules/moduleB/index.d.ts</code></li>
<li><code>/node_modules/moduleB.ts</code></li>
<li><code>/node_modules/moduleB.tsx</code></li>
<li><code>/node_modules/moduleB.d.ts</code></li>
<li><code>/node_modules/moduleB/package.json</code> (如果指定了<code>&quot;types&quot;</code>属性)</li>
<li><code>/node_modules/moduleB/index.ts</code></li>
<li><code>/node_modules/moduleB/index.tsx</code></li>
<li><code>/node_modules/moduleB/index.d.ts</code></li>
</ol>
<p>不要被这里步骤的数量吓到 - TypeScript只是在步骤（8）和（15）向上跳了两次目录。 这并不比Node.js里的流程复杂。</p>
<h4 id="附加的模块解析标记"><a href="#附加的模块解析标记" class="headerlink" title="附加的模块解析标记"></a>附加的模块解析标记</h4><p>有时工程源码结构与输出结构不同。 通常是要经过一系统的构建步骤最后生成输出。 它们包括将 <code>.ts</code>编译成<code>.js</code>，将不同位置的依赖拷贝至一个输出位置。 最终结果就是运行时的模块名与包含它们声明的源文件里的模块名不同。 或者最终输出文件里的模块路径与编译时的源文件路径不同了。</p>
<p>TypeScript编译器有一些额外的标记用来<em>通知</em>编译器在源码编译成最终输出的过程中都发生了哪个转换。</p>
<p>有一点要特别注意的是编译器<em>不会</em>进行这些转换操作； 它只是利用这些信息来指导模块的导入。</p>
<p>Base URL： </p>
<p>在利用AMD模块加载器的应用里使用<code>baseUrl</code>是常见做法，它要求在运行时模块都被放到了一个文件夹里。 这些模块的源码可以在不同的目录下，但是构建脚本会将它们集中到一起。</p>
<p>设置<code>baseUrl</code>来告诉编译器到哪里去查找模块。 所有非相对模块导入都会被当做相对于 <code>baseUrl</code>。</p>
<p><em>baseUrl</em>的值由以下两者之一决定：</p>
<ul>
<li>命令行中<em>baseUrl</em>的值（如果给定的路径是相对的，那么将相对于当前路径进行计算）</li>
<li>‘tsconfig.json’里的<em>baseUrl</em>属性（如果给定的路径是相对的，那么将相对于‘tsconfig.json’路径进行计算）</li>
</ul>
<p>注意相对模块的导入不会被设置的<code>baseUrl</code>所影响，因为它们总是相对于导入它们的文件。</p>
<p>阅读更多关于<code>baseUrl</code>的信息<a href="http://requirejs.org/docs/api.html#config-baseUrl" target="_blank" rel="noopener">RequireJS</a>和 <a href="https://github.com/systemjs/systemjs/blob/master/docs/config-api.md#baseurl" target="_blank" rel="noopener">SystemJS</a>。</p>
<p>路径映射：</p>
<p>有时模块不是直接放在<em>baseUrl</em>下面。 比如，充分 <code>&quot;jquery&quot;</code>模块地导入，在运行时可能被解释为<code>&quot;node_modules/jquery/dist/jquery.slim.min.js&quot;</code>。 加载器使用映射配置来将模块名映射到运行时的文件，查看 <a href="http://requirejs.org/docs/api.html#config-paths" target="_blank" rel="noopener">RequireJs documentation</a>和<a href="https://github.com/systemjs/systemjs/blob/master/docs/config-api.md#paths" target="_blank" rel="noopener">SystemJS documentation</a>。</p>
<p>TypeScript编译器通过使用<code>tsconfig.json</code>文件里的<code>&quot;paths&quot;</code>来支持这样的声明映射。 下面是一个如何指定 <code>jquery</code>的<code>&quot;paths&quot;</code>的例子。</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"compilerOptions"</span>: &#123;</span><br><span class="line">    <span class="attr">"baseUrl"</span>: <span class="string">"."</span>, <span class="comment">// This must be specified if "paths" is.</span></span><br><span class="line">    <span class="attr">"paths"</span>: &#123;</span><br><span class="line">      <span class="attr">"jquery"</span>: [<span class="string">"node_modules/jquery/dist/jquery"</span>] <span class="comment">// 此处映射是相对于"baseUrl"</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>请注意<code>&quot;paths&quot;</code>是相对于<code>&quot;baseUrl&quot;</code>进行解析。 如果 <code>&quot;baseUrl&quot;</code>被设置成了除<code>&quot;.&quot;</code>外的其它值，比如<code>tsconfig.json</code>所在的目录，那么映射必须要做相应的改变。 如果你在上例中设置了 <code>&quot;baseUrl&quot;: &quot;./src&quot;</code>，那么jquery应该映射到<code>&quot;../node_modules/jquery/dist/jquery&quot;</code>。</p>
<p>通过<code>&quot;paths&quot;</code>我们还可以指定复杂的映射，包括指定多个回退位置。 假设在一个工程配置里，有一些模块位于一处，而其它的则在另个的位置。 构建过程会将它们集中至一处。 工程结构可能如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">projectRoot</span><br><span class="line">├── folder1</span><br><span class="line">│   ├── file1.ts (imports &#39;folder1&#x2F;file2&#39; and &#39;folder2&#x2F;file3&#39;)</span><br><span class="line">│   └── file2.ts</span><br><span class="line">├── generated</span><br><span class="line">│   ├── folder1</span><br><span class="line">│   └── folder2</span><br><span class="line">│       └── file3.ts</span><br><span class="line">└── tsconfig.json</span><br></pre></td></tr></table></figure>

<p>相应的<code>tsconfig.json</code>文件如下：</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"compilerOptions"</span>: &#123;</span><br><span class="line">    <span class="attr">"baseUrl"</span>: <span class="string">"."</span>,</span><br><span class="line">    <span class="attr">"paths"</span>: &#123;</span><br><span class="line">      <span class="attr">"*"</span>: [</span><br><span class="line">        <span class="string">"*"</span>,</span><br><span class="line">        <span class="string">"generated/*"</span></span><br><span class="line">      ]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>它告诉编译器所有匹配<code>&quot;*&quot;</code>（所有的值）模式的模块导入会在以下两个位置查找：</p>
<ol>
<li><code>&quot;*&quot;</code>： 表示名字不发生改变，所以映射为<code>&lt;moduleName&gt;</code> =&gt; <code>&lt;baseUrl&gt;/&lt;moduleName&gt;</code></li>
<li><code>&quot;generated/*&quot;</code>表示模块名添加了“generated”前缀，所以映射为<code>&lt;moduleName&gt;</code> =&gt; <code>&lt;baseUrl&gt;/generated/&lt;moduleName&gt;</code></li>
</ol>
<p>按照这个逻辑，编译器将会如下尝试解析这两个导入：</p>
<ul>
<li>导入’folder1/file2’<ol>
<li>匹配’*’模式且通配符捕获到整个名字。</li>
<li>尝试列表里的第一个替换：’*’ -&gt; <code>folder1/file2</code>。</li>
<li>替换结果为非相对名 - 与<em>baseUrl</em>合并 -&gt; <code>projectRoot/folder1/file2.ts</code>。</li>
<li>文件存在。完成。</li>
</ol>
</li>
<li>导入’folder2/file3’<ol>
<li>匹配’*’模式且通配符捕获到整个名字。</li>
<li>尝试列表里的第一个替换：’*’ -&gt; <code>folder2/file3</code>。</li>
<li>替换结果为非相对名 - 与<em>baseUrl</em>合并 -&gt; <code>projectRoot/folder2/file3.ts</code>。</li>
<li>文件不存在，跳到第二个替换。</li>
<li>第二个替换：’generated/*’ -&gt; <code>generated/folder2/file3</code>。</li>
<li>替换结果为非相对名 - 与<em>baseUrl</em>合并 -&gt; <code>projectRoot/generated/folder2/file3.ts</code>。</li>
<li>文件存在。完成。</li>
</ol>
</li>
</ul>
<p>利用<code>rootDirs</code>指定虚拟目录：</p>
<p>有时多个目录下的工程源文件在编译时会进行合并放在某个输出目录下。 这可以看做一些源目录创建了一个“虚拟”目录。</p>
<p>利用<code>rootDirs</code>，可以告诉编译器生成这个虚拟目录的<em>roots</em>； 因此编译器可以在“虚拟”目录下解析相对模块导入，就 <em>好像</em>它们被合并在了一起一样。</p>
<p>比如，有下面的工程结构：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"> src</span><br><span class="line">└── views</span><br><span class="line">    └── view1.ts (imports &#39;.&#x2F;template1&#39;)</span><br><span class="line">    └── view2.ts</span><br><span class="line"></span><br><span class="line">generated</span><br><span class="line">└── templates</span><br><span class="line">        └── views</span><br><span class="line">            └── template1.ts (imports &#39;.&#x2F;view2&#39;)</span><br></pre></td></tr></table></figure>

<p><code>src/views</code>里的文件是用于控制UI的用户代码。 <code>generated/templates</code>是UI模版，在构建时通过模版生成器自动生成。 构建中的一步会将 <code>/src/views</code>和<code>/generated/templates/views</code>的输出拷贝到同一个目录下。 在运行时，视图可以假设它的模版与它同在一个目录下，因此可以使用相对导入 <code>&quot;./template&quot;</code>。</p>
<p>可以使用<code>&quot;rootDirs&quot;</code>来告诉编译器。 <code>&quot;rootDirs&quot;</code>指定了一个<em>roots</em>列表，列表里的内容会在运行时被合并。 因此，针对这个例子， <code>tsconfig.json</code>如下：</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"compilerOptions"</span>: &#123;</span><br><span class="line">    <span class="attr">"rootDirs"</span>: [</span><br><span class="line">      <span class="string">"src/views"</span>,</span><br><span class="line">      <span class="string">"generated/templates/views"</span></span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>每当编译器在某一<code>rootDirs</code>的子目录下发现了相对模块导入，它就会尝试从每一个<code>rootDirs</code>中导入。</p>
<p><code>rootDirs</code>的灵活性不仅仅局限于其指定了要在逻辑上合并的物理目录列表。它提供的数组可以包含任意数量的任何名字的目录，不论它们是否存在。这允许编译器以类型安全的方式处理复杂捆绑(bundles)和运行时的特性，比如条件引入和工程特定的加载器插件。</p>
<p>设想这样一个国际化的场景，构建工具自动插入特定的路径记号来生成针对不同区域的捆绑，比如将<code>#{locale}</code>做为相对模块路径<code>./#{locale}/messages</code>的一部分。在这个假定的设置下，工具会枚举支持的区域，将抽像的路径映射成<code>./zh/messages</code>，<code>./de/messages</code>等。</p>
<p>假设每个模块都会导出一个字符串的数组。比如<code>./zh/messages</code>可能包含：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">    <span class="string">"您好吗"</span>,</span><br><span class="line">    <span class="string">"很高兴认识你"</span></span><br><span class="line">];</span><br></pre></td></tr></table></figure>

<p>利用<code>rootDirs</code>我们可以让编译器了解这个映射关系，从而也允许编译器能够安全地解析<code>./#{locale}/messages</code>，就算这个目录永远都不存在。比如，使用下面的<code>tsconfig.json</code>：</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">"compilerOptions"</span>: &#123;</span><br><span class="line">    <span class="attr">"rootDirs"</span>: [</span><br><span class="line">      <span class="string">"src/zh"</span>,</span><br><span class="line">      <span class="string">"src/de"</span>,</span><br><span class="line">      <span class="string">"src/#&#123;locale&#125;"</span></span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>编译器现在可以将<code>import messages from &#39;./#{locale}/messages&#39;</code>解析为<code>import messages from &#39;./zh/messages&#39;</code>用做工具支持的目的，并允许在开发时不必了解区域信息。</p>
<h3 id="跟踪模块解析"><a href="#跟踪模块解析" class="headerlink" title="跟踪模块解析"></a>跟踪模块解析</h3><p>如之前讨论，编译器在解析模块时可能访问当前文件夹外的文件。 这会导致很难诊断模块为什么没有被解析，或解析到了错误的位置。 通过 <code>--traceResolution</code>启用编译器的模块解析跟踪，它会告诉我们在模块解析过程中发生了什么。</p>
<p>假设我们有一个使用了<code>typescript</code>模块的简单应用。 <code>app.ts</code>里有一个这样的导入<code>import * as ts from &quot;typescript&quot;</code>。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">│   tsconfig.json</span><br><span class="line">├───node_modules</span><br><span class="line">│   └───typescript</span><br><span class="line">│       └───lib</span><br><span class="line">│               typescript.d.ts</span><br><span class="line">└───src</span><br><span class="line">        app.ts</span><br></pre></td></tr></table></figure>

<p>使用<code>--traceResolution</code>调用编译器。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tsc --traceResolution</span><br></pre></td></tr></table></figure>

<p>输出结果如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D; Resolving module &#39;typescript&#39; from &#39;src&#x2F;app.ts&#39;. &#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</span><br><span class="line">Module resolution kind is not specified, using &#39;NodeJs&#39;.</span><br><span class="line">Loading module &#39;typescript&#39; from &#39;node_modules&#39; folder.</span><br><span class="line">File &#39;src&#x2F;node_modules&#x2F;typescript.ts&#39; does not exist.</span><br><span class="line">File &#39;src&#x2F;node_modules&#x2F;typescript.tsx&#39; does not exist.</span><br><span class="line">File &#39;src&#x2F;node_modules&#x2F;typescript.d.ts&#39; does not exist.</span><br><span class="line">File &#39;src&#x2F;node_modules&#x2F;typescript&#x2F;package.json&#39; does not exist.</span><br><span class="line">File &#39;node_modules&#x2F;typescript.ts&#39; does not exist.</span><br><span class="line">File &#39;node_modules&#x2F;typescript.tsx&#39; does not exist.</span><br><span class="line">File &#39;node_modules&#x2F;typescript.d.ts&#39; does not exist.</span><br><span class="line">Found &#39;package.json&#39; at &#39;node_modules&#x2F;typescript&#x2F;package.json&#39;.</span><br><span class="line">&#39;package.json&#39; has &#39;types&#39; field &#39;.&#x2F;lib&#x2F;typescript.d.ts&#39; that references &#39;node_modules&#x2F;typescript&#x2F;lib&#x2F;typescript.d.ts&#39;.</span><br><span class="line">File &#39;node_modules&#x2F;typescript&#x2F;lib&#x2F;typescript.d.ts&#39; exist - use it as a module resolution result.</span><br><span class="line">&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D; Module name &#39;typescript&#39; was successfully resolved to &#39;node_modules&#x2F;typescript&#x2F;lib&#x2F;typescript.d.ts&#39;. &#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</span><br></pre></td></tr></table></figure>

<p>需要留意的地方</p>
<ul>
<li>导入的名字及位置</li>
</ul>
<blockquote>
<p>======== Resolving module <strong>‘typescript’</strong> from <strong>‘src/app.ts’</strong>. ========</p>
</blockquote>
<ul>
<li>编译器使用的策略</li>
</ul>
<blockquote>
<p>Module resolution kind is not specified, using <strong>‘NodeJs’</strong>.</p>
</blockquote>
<ul>
<li>从npm加载types</li>
</ul>
<blockquote>
<p>‘package.json’ has <strong>‘types’</strong> field ‘./lib/typescript.d.ts’ that references ‘node_modules/typescript/lib/typescript.d.ts’.</p>
</blockquote>
<ul>
<li>最终结果</li>
</ul>
<blockquote>
<p>======== Module name ‘typescript’ was <strong>successfully resolved</strong> to ‘node_modules/typescript/lib/typescript.d.ts’. ========</p>
</blockquote>
<p>使用<code>--noResolve</code></p>
<p>正常来讲编译器会在开始编译之前解析模块导入。 每当它成功地解析了对一个文件 <code>import</code>，这个文件被会加到一个文件列表里，以供编译器稍后处理。</p>
<p><code>--noResolve</code>编译选项告诉编译器不要添加任何不是在命令行上传入的文件到编译列表。 编译器仍然会尝试解析模块，但是只要没有指定这个文件，那么它就不会被包含在内。</p>
<p>比如</p>
<p>app.ts</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> A <span class="keyword">from</span> <span class="string">"moduleA"</span> <span class="comment">// OK, moduleA passed on the command-line</span></span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> B <span class="keyword">from</span> <span class="string">"moduleB"</span> <span class="comment">// Error TS2307: Cannot find module 'moduleB'.</span></span><br><span class="line">tsc app.ts moduleA.ts --noResolve</span><br></pre></td></tr></table></figure>

<p>使用<code>--noResolve</code>编译<code>app.ts</code>：</p>
<ul>
<li>可能正确找到<code>moduleA</code>，因为它在命令行上指定了。</li>
<li>找不到<code>moduleB</code>，因为没有在命令行上传递。</li>
</ul>
<h3 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h3><p>为什么在<code>exclude</code>列表里的模块还会被编译器使用：</p>
<p><code>tsconfig.json</code>将文件夹转变一个“工程” 如果不指定任何 <code>“exclude”</code>或<code>“files”</code>，文件夹里的所有文件包括<code>tsconfig.json</code>和所有的子目录都会在编译列表里。 如果你想利用 <code>“exclude”</code>排除某些文件，甚至你想指定所有要编译的文件列表，请使用<code>“files”</code>。</p>
<p>有些是被<code>tsconfig.json</code>自动加入的。 它不会涉及到上面讨论的模块解析。 如果编译器识别出一个文件是模块导入目标，它就会加到编译列表里，不管它是否被排除了。</p>
<p>因此，要从编译列表中排除一个文件，你需要在排除它的同时，还要排除所有对它进行<code>import</code>或使用了<code>/// &lt;reference path=&quot;...&quot; /&gt;</code>指令的文件。</p>
<h2 id="声明合并"><a href="#声明合并" class="headerlink" title="声明合并"></a>声明合并</h2><p>TypeScript中有些独特的概念可以在类型层面上描述JavaScript对象的模型。 这其中尤其独特的一个例子是“声明合并”的概念。 理解了这个概念，将有助于操作现有的JavaScript代码。 同时，也会有助于理解更多高级抽象的概念。</p>
<p>对本文件来讲，“声明合并”是指编译器将针对同一个名字的两个独立声明合并为单一声明。 合并后的声明同时拥有原先两个声明的特性。 任何数量的声明都可被合并；不局限于两个声明。</p>
<p>TypeScript中的声明会创建以下三种实体之一：命名空间，类型或值。 创建命名空间的声明会新建一个命名空间，它包含了用（.）符号来访问时使用的名字。 创建类型的声明是：用声明的模型创建一个类型并绑定到给定的名字上。 最后，创建值的声明会创建在JavaScript输出中看到的值。</p>
<table>
<thead>
<tr>
<th align="left">Declaration Type</th>
<th align="center">Namespace</th>
<th align="center">Type</th>
<th align="center">Value</th>
</tr>
</thead>
<tbody><tr>
<td align="left">Namespace</td>
<td align="center">X</td>
<td align="center"></td>
<td align="center">X</td>
</tr>
<tr>
<td align="left">Class</td>
<td align="center"></td>
<td align="center">X</td>
<td align="center">X</td>
</tr>
<tr>
<td align="left">Enum</td>
<td align="center"></td>
<td align="center">X</td>
<td align="center">X</td>
</tr>
<tr>
<td align="left">Interface</td>
<td align="center"></td>
<td align="center">X</td>
<td align="center"></td>
</tr>
<tr>
<td align="left">Type Alias</td>
<td align="center"></td>
<td align="center">X</td>
<td align="center"></td>
</tr>
<tr>
<td align="left">Function</td>
<td align="center"></td>
<td align="center"></td>
<td align="center">X</td>
</tr>
<tr>
<td align="left">Variable</td>
<td align="center"></td>
<td align="center"></td>
<td align="center">X</td>
</tr>
</tbody></table>
<p>理解每个声明创建了什么，有助于理解当声明合并时有哪些东西被合并了。</p>
<h3 id="合并接口"><a href="#合并接口" class="headerlink" title="合并接口"></a>合并接口</h3><p>最简单也最常见的声明合并类型是接口合并。 从根本上说，合并的机制是把双方的成员放到一个同名的接口里。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Box &#123;</span><br><span class="line">    height: <span class="built_in">number</span>;</span><br><span class="line">    width: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Box &#123;</span><br><span class="line">    scale: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> box: Box = &#123;height: <span class="number">5</span>, width: <span class="number">6</span>, scale: <span class="number">10</span>&#125;;</span><br></pre></td></tr></table></figure>

<p>接口的非函数的成员应该是唯一的。如果它们不是唯一的，那么它们必须是相同的类型。如果两个接口中同时声明了同名的非函数成员且它们的类型不同，则编译器会报错。</p>
<p>对于函数成员，每个同名函数声明都会被当成这个函数的一个重载。 同时需要注意，当接口 <code>A</code>与后来的接口 <code>A</code>合并时，后面的接口具有更高的优先级。</p>
<p>如下例所示：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Cloner &#123;</span><br><span class="line">    clone(animal: Animal): Animal;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Cloner &#123;</span><br><span class="line">    clone(animal: Sheep): Sheep;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Cloner &#123;</span><br><span class="line">    clone(animal: Dog): Dog;</span><br><span class="line">    clone(animal: Cat): Cat;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这三个接口合并成一个声明：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Cloner &#123;</span><br><span class="line">    clone(animal: Dog): Dog;</span><br><span class="line">    clone(animal: Cat): Cat;</span><br><span class="line">    clone(animal: Sheep): Sheep;</span><br><span class="line">    clone(animal: Animal): Animal;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>注意每组接口里的声明顺序保持不变，但各组接口之间的顺序是后来的接口重载出现在靠前位置。</strong></p>
<p>这个规则有一个例外是当出现特殊的函数签名时。 如果签名里有一个参数的类型是 <em>单一</em>的字符串字面量（比如，不是字符串字面量的联合类型），那么它将会被提升到重载列表的最顶端。</p>
<p>比如，下面的接口会合并到一起：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Document &#123;</span><br><span class="line">    createElement(tagName: <span class="built_in">any</span>): Element;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> Document &#123;</span><br><span class="line">    createElement(tagName: <span class="string">"div"</span>): HTMLDivElement;</span><br><span class="line">    createElement(tagName: <span class="string">"span"</span>): HTMLSpanElement;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">interface</span> Document &#123;</span><br><span class="line">    createElement(tagName: <span class="built_in">string</span>): HTMLElement;</span><br><span class="line">    createElement(tagName: <span class="string">"canvas"</span>): HTMLCanvasElement;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>合并后的 <code>Document</code>将会像下面这样：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> Document &#123;</span><br><span class="line">    createElement(tagName: <span class="string">"canvas"</span>): HTMLCanvasElement;</span><br><span class="line">    createElement(tagName: <span class="string">"div"</span>): HTMLDivElement;</span><br><span class="line">    createElement(tagName: <span class="string">"span"</span>): HTMLSpanElement;</span><br><span class="line">    createElement(tagName: <span class="built_in">string</span>): HTMLElement;</span><br><span class="line">    createElement(tagName: <span class="built_in">any</span>): Element;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="合并命名空间"><a href="#合并命名空间" class="headerlink" title="合并命名空间"></a>合并命名空间</h3><p>与接口相似，同名的命名空间也会合并其成员。 命名空间会创建出命名空间和值，我们需要知道这两者都是怎么合并的。</p>
<p>对于命名空间的合并，模块导出的同名接口进行合并，构成单一命名空间内含合并后的接口。</p>
<p>对于命名空间里值的合并，如果当前已经存在给定名字的命名空间，那么后来的命名空间的导出成员会被加到已经存在的那个模块里。</p>
<p><code>Animals</code>声明合并示例：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> Animals &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">class</span> Zebra &#123; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> Animals &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">interface</span> Legged &#123; numberOfLegs: <span class="built_in">number</span>; &#125;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">class</span> Dog &#123; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>等同于：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> Animals &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">interface</span> Legged &#123; numberOfLegs: <span class="built_in">number</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">class</span> Zebra &#123; &#125;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">class</span> Dog &#123; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>除了这些合并外，你还需要了解非导出成员是如何处理的。 非导出成员仅在其原有的（合并前的）命名空间内可见。这就是说合并之后，从其它命名空间合并进来的成员无法访问非导出成员。</p>
<p>下例提供了更清晰的说明：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> Animal &#123;</span><br><span class="line">    <span class="keyword">let</span> haveMuscles = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">animalsHaveMuscles</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> haveMuscles;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> Animal &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">doAnimalsHaveMuscles</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> haveMuscles;  <span class="comment">// Error, because haveMuscles is not accessible here</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>因为 <code>haveMuscles</code>并没有导出，只有 <code>animalsHaveMuscles</code>函数共享了原始未合并的命名空间可以访问这个变量。 <code>doAnimalsHaveMuscles</code>函数虽是合并命名空间的一部分，但是访问不了未导出的成员。</p>
<h3 id="命名空间与类和函数和枚举类型合并"><a href="#命名空间与类和函数和枚举类型合并" class="headerlink" title="命名空间与类和函数和枚举类型合并"></a>命名空间与类和函数和枚举类型合并</h3><p>命名空间可以与其它类型的声明进行合并。 只要命名空间的定义符合将要合并类型的定义。合并结果包含两者的声明类型。 TypeScript使用这个功能去实现一些JavaScript里的设计模式。</p>
<h4 id="合并命名空间和类"><a href="#合并命名空间和类" class="headerlink" title="合并命名空间和类"></a>合并命名空间和类</h4><p>这让我们可以表示内部类。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Album &#123;</span><br><span class="line">    label: Album.AlbumLabel;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">namespace</span> Album &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">class</span> AlbumLabel &#123; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>合并规则与上面 <code>合并命名空间</code>小节里讲的规则一致，我们必须导出 <code>AlbumLabel</code>类，好让合并的类能访问。 合并结果是一个类并带有一个内部类。 你也可以使用命名空间为类增加一些静态属性。</p>
<p>除了内部类的模式，你在JavaScript里，创建一个函数稍后扩展它增加一些属性也是很常见的。 TypeScript使用声明合并来达到这个目的并保证类型安全。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">buildLabel</span>(<span class="params">name: <span class="built_in">string</span></span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> buildLabel.prefix + name + buildLabel.suffix;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> buildLabel &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">let</span> suffix = <span class="string">""</span>;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">let</span> prefix = <span class="string">"Hello, "</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(buildLabel(<span class="string">"Sam Smith"</span>));</span><br></pre></td></tr></table></figure>

<p>相似的，命名空间可以用来扩展枚举型：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">enum</span> Color &#123;</span><br><span class="line">    red = <span class="number">1</span>,</span><br><span class="line">    green = <span class="number">2</span>,</span><br><span class="line">    blue = <span class="number">4</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> Color &#123;</span><br><span class="line">    <span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">mixColor</span>(<span class="params">colorName: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (colorName == <span class="string">"yellow"</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> Color.red + Color.green;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (colorName == <span class="string">"white"</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> Color.red + Color.green + Color.blue;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (colorName == <span class="string">"magenta"</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> Color.red + Color.blue;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (colorName == <span class="string">"cyan"</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> Color.green + Color.blue;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="非法的合并"><a href="#非法的合并" class="headerlink" title="非法的合并"></a>非法的合并</h4><p>TypeScript并非允许所有的合并。 目前，类不能与其它类或变量合并。 想要了解如何模仿类的合并，请参考 <a href="https://www.tslang.cn/docs/handbook/mixins.html" target="_blank" rel="noopener">TypeScript的混入</a>。</p>
<h2 id="模块扩展"><a href="#模块扩展" class="headerlink" title="模块扩展"></a>模块扩展</h2><p>虽然JavaScript不支持合并，但你可以为导入的对象打补丁以更新它们。让我们考察一下这个玩具性的示例：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// observable.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">Observable</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">    <span class="comment">// ... implementation left as an exercise for the reader ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// map.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; Observable &#125; <span class="keyword">from</span> <span class="string">"./observable"</span>;</span><br><span class="line">Observable.prototype.map = <span class="function"><span class="keyword">function</span> (<span class="params">f</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// ... another exercise for the reader</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>它也可以很好地工作在TypeScript中， 但编译器对 <code>Observable.prototype.map</code>一无所知。 你可以使用扩展模块来将它告诉编译器：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// observable.ts stays the same</span></span><br><span class="line"><span class="comment">// map.ts</span></span><br><span class="line"><span class="keyword">import</span> &#123; Observable &#125; <span class="keyword">from</span> <span class="string">"./observable"</span>;</span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">module</span> "./observable" &#123;</span><br><span class="line">    <span class="keyword">interface</span> Observable&lt;T&gt; &#123;</span><br><span class="line">        map&lt;U&gt;<span class="function">(<span class="params">f: (<span class="params">x: T</span>) =&gt; U</span>): <span class="params">Observable</span>&lt;<span class="params">U</span>&gt;;</span></span><br><span class="line"><span class="function">    &#125;</span></span><br><span class="line"><span class="function">&#125;</span></span><br><span class="line"><span class="function"><span class="params">Observable</span>.<span class="params">prototype</span>.<span class="params">map</span> = <span class="params">function</span> (<span class="params">f</span>) &#123;</span></span><br><span class="line"><span class="function">    // ... <span class="params">another</span> <span class="params">exercise</span> <span class="params">for</span> <span class="params">the</span> <span class="params">reader</span></span></span><br><span class="line"><span class="function">&#125;</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function">// <span class="params">consumer</span>.<span class="params">ts</span></span></span><br><span class="line"><span class="function"><span class="params">import</span> &#123; <span class="params">Observable</span> &#125; <span class="params">from</span> "./<span class="params">observable</span>";</span></span><br><span class="line"><span class="function"><span class="params">import</span> "./<span class="params">map</span>";</span></span><br><span class="line"><span class="function"><span class="params">let</span> <span class="params">o</span>: <span class="params">Observable</span>&lt;<span class="params">number</span>&gt;;</span></span><br><span class="line"><span class="function"><span class="params">o</span>.<span class="params">map</span>(<span class="params">x =&gt; x.toFixed(<span class="params"></span>)</span>);</span></span><br></pre></td></tr></table></figure>

<p>模块名的解析和用 <code>import</code>/ <code>export</code>解析模块标识符的方式是一致的。 更多信息请参考 <a href="https://www.tslang.cn/docs/handbook/modules.html" target="_blank" rel="noopener">Modules</a>。 当这些声明在扩展中合并时，就好像在原始位置被声明了一样。 但是，你不能在扩展中声明新的顶级声明－仅可以扩展模块中已经存在的声明。</p>
<h3 id="全局扩展"><a href="#全局扩展" class="headerlink" title="全局扩展"></a>全局扩展</h3><p>你也以在模块内部添加声明到全局作用域中。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// observable.ts</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> Observable&lt;T&gt; &#123;</span><br><span class="line">    <span class="comment">// ... still no implementation ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">declare</span> global &#123;</span><br><span class="line">    <span class="keyword">interface</span> Array&lt;T&gt; &#123;</span><br><span class="line">        toObservable(): Observable&lt;T&gt;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">Array</span>.prototype.toObservable = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>全局扩展与模块扩展的行为和限制是相同的。</p>
<h2 id="JSX"><a href="#JSX" class="headerlink" title="JSX"></a>JSX</h2><p><a href="https://facebook.github.io/jsx/" target="_blank" rel="noopener">JSX</a>是一种嵌入式的类似XML的语法。 它可以被转换成合法的JavaScript，尽管转换的语义是依据不同的实现而定的。 JSX因<a href="https://reactjs.org/" target="_blank" rel="noopener">React</a>框架而流行，但也存在其它的实现。 TypeScript支持内嵌，类型检查以及将JSX直接编译为JavaScript。</p>
<p>想要使用JSX必须做两件事：</p>
<ol>
<li>给文件一个<code>.tsx</code>扩展名</li>
<li>启用<code>jsx</code>选项</li>
</ol>
<p>TypeScript具有三种JSX模式：<code>preserve</code>，<code>react</code>和<code>react-native</code>。 这些模式只在代码生成阶段起作用 - 类型检查并不受影响。 在<code>preserve</code>模式下生成代码中会保留JSX以供后续的转换操作使用（比如：<a href="https://babeljs.io/" target="_blank" rel="noopener">Babel</a>）。 另外，输出文件会带有<code>.jsx</code>扩展名。 <code>react</code>模式会生成<code>React.createElement</code>，在使用前不需要再进行转换操作了，输出文件的扩展名为<code>.js</code>。 <code>react-native</code>相当于<code>preserve</code>，它也保留了所有的JSX，但是输出文件的扩展名是<code>.js</code>。</p>
<table>
<thead>
<tr>
<th align="left">模式</th>
<th align="left">输入</th>
<th align="left">输出</th>
<th align="left">输出文件扩展名</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><code>preserve</code></td>
<td align="left"><code>&lt;div /&gt;</code></td>
<td align="left"><code>&lt;div /&gt;</code></td>
<td align="left"><code>.jsx</code></td>
</tr>
<tr>
<td align="left"><code>react</code></td>
<td align="left"><code>&lt;div /&gt;</code></td>
<td align="left"><code>React.createElement(&quot;div&quot;)</code></td>
<td align="left"><code>.js</code></td>
</tr>
<tr>
<td align="left"><code>react-native</code></td>
<td align="left"><code>&lt;div /&gt;</code></td>
<td align="left"><code>&lt;div /&gt;</code></td>
<td align="left"><code>.js</code></td>
</tr>
</tbody></table>
<p>你可以通过在命令行里使用<code>--jsx</code>标记或<a href="https://www.tslang.cn/docs/handbook/tsconfig-json.html" target="_blank" rel="noopener">tsconfig.json</a>里的选项来指定模式。</p>
<blockquote>
<p><em>注意：<code>React</code>标识符是写死的硬编码，所以你必须保证React（大写的R）是可用的。</em></p>
</blockquote>
<p>回想一下怎么写类型断言：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> foo = &lt;foo&gt;bar;</span><br></pre></td></tr></table></figure>

<p>这里断言<code>bar</code>变量是<code>foo</code>类型的。 因为TypeScript也使用尖括号来表示类型断言，在结合JSX的语法后将带来解析上的困难。因此，TypeScript在<code>.tsx</code>文件里禁用了使用尖括号的类型断言。</p>
<p>由于不能够在<code>.tsx</code>文件里使用上述语法，因此我们应该使用另一个类型断言操作符：<code>as</code>。 上面的例子可以很容易地使用<code>as</code>操作符改写：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> foo = bar <span class="keyword">as</span> foo;</span><br></pre></td></tr></table></figure>

<p><code>as</code>操作符在<code>.ts</code>和<code>.tsx</code>里都可用，并且与尖括号类型断言行为是等价的。</p>
<p>类型检查：</p>
<p>为了理解JSX的类型检查，你必须首先理解固有元素与基于值的元素之间的区别。 假设有这样一个JSX表达式<code>&lt;expr /&gt;</code>，<code>expr</code>可能引用环境自带的某些东西（比如，在DOM环境里的<code>div</code>或<code>span</code>）或者是你自定义的组件。 这是非常重要的，原因有如下两点：</p>
<ol>
<li>对于React，固有元素会生成字符串（<code>React.createElement(&quot;div&quot;)</code>），然而由你自定义的组件却不会生成（<code>React.createElement(MyComponent)</code>）。</li>
<li>传入JSX元素里的属性类型的查找方式不同。 固有元素属性<em>本身</em>就支持，然而自定义的组件会自己去指定它们具有哪个属性。</li>
</ol>
<p>TypeScript使用<a href="http://facebook.github.io/react/docs/jsx-in-depth.html#html-tags-vs.-react-components" target="_blank" rel="noopener">与React相同的规范</a> 来区别它们。 固有元素总是以一个小写字母开头，基于值的元素总是以一个大写字母开头。</p>
<h3 id="固有元素"><a href="#固有元素" class="headerlink" title="固有元素"></a>固有元素</h3><p>固有元素使用特殊的接口<code>JSX.IntrinsicElements</code>来查找。 默认地，如果这个接口没有指定，会全部通过，不对固有元素进行类型检查。 然而，如果这个接口存在，那么固有元素的名字需要在<code>JSX.IntrinsicElements</code>接口的属性里查找。 例如：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">namespace</span> JSX &#123;</span><br><span class="line">    <span class="keyword">interface</span> IntrinsicElements &#123;</span><br><span class="line">        foo: <span class="built_in">any</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&lt;foo /&gt;; <span class="comment">// 正确</span></span><br><span class="line">&lt;bar /&gt;; <span class="comment">// 错误</span></span><br></pre></td></tr></table></figure>

<p>在上例中，<code>&lt;foo /&gt;</code>没有问题，但是<code>&lt;bar /&gt;</code>会报错，因为它没在<code>JSX.IntrinsicElements</code>里指定。</p>
<blockquote>
<p>注意：你也可以在<code>JSX.IntrinsicElements</code>上指定一个用来捕获所有字符串索引：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">namespace</span> JSX &#123;</span><br><span class="line">    <span class="keyword">interface</span> IntrinsicElements &#123;</span><br><span class="line">        [elemName: <span class="built_in">string</span>]: <span class="built_in">any</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</blockquote>
<h3 id="基于值的元素"><a href="#基于值的元素" class="headerlink" title="基于值的元素"></a>基于值的元素</h3><p>基于值的元素会简单的在它所在的作用域里按标识符查找。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> MyComponent <span class="keyword">from</span> <span class="string">"./myComponent"</span>;</span><br><span class="line">                                </span><br><span class="line">&lt;MyComponent /&gt;; <span class="comment">// 正确</span></span><br><span class="line">&lt;SomeOtherComponent /&gt;; <span class="comment">// 错误</span></span><br></pre></td></tr></table></figure>

<p>有两种方式可以定义基于值的元素：</p>
<ol>
<li>无状态函数组件 (SFC)</li>
<li>类组件</li>
</ol>
<p>由于这两种基于值的元素在JSX表达式里无法区分，因此TypeScript首先会尝试将表达式做为无状态函数组件进行解析。如果解析成功，那么TypeScript就完成了表达式到其声明的解析操作。如果按照无状态函数组件解析失败，那么TypeScript会继续尝试以类组件的形式进行解析。如果依旧失败，那么将输出一个错误。</p>
<h3 id="无状态函数组件"><a href="#无状态函数组件" class="headerlink" title="无状态函数组件"></a>无状态函数组件</h3><p>正如其名，组件被定义成JavaScript函数，它的第一个参数是<code>props</code>对象。 TypeScript会强制它的返回值可以赋值给<code>JSX.Element</code>。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> FooProp &#123;</span><br><span class="line">    name: <span class="built_in">string</span>;</span><br><span class="line">    X: <span class="built_in">number</span>;</span><br><span class="line">    Y: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">declare</span> <span class="function"><span class="keyword">function</span> <span class="title">AnotherComponent</span>(<span class="params">prop: &#123;name: <span class="built_in">string</span>&#125;</span>)</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">ComponentFoo</span>(<span class="params">prop: FooProp</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> &lt;AnotherComponent name=&#123;prop.name&#125; /&gt;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> Button = <span class="function">(<span class="params">prop: &#123;value: <span class="built_in">string</span>&#125;, context: &#123; color: <span class="built_in">string</span> &#125;</span>) =&gt;</span> &lt;button&gt;</span><br></pre></td></tr></table></figure>

<p>由于无状态函数组件是简单的JavaScript函数，所以我们还可以利用函数重载。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> ClickableProps &#123;</span><br><span class="line">    children: JSX.Element[] | JSX.Element</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> HomeProps <span class="keyword">extends</span> ClickableProps &#123;</span><br><span class="line">    home: JSX.Element;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> SideProps <span class="keyword">extends</span> ClickableProps &#123;</span><br><span class="line">    side: JSX.Element | <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">MainButton</span>(<span class="params">prop: HomeProps</span>): <span class="title">JSX</span>.<span class="title">Element</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">MainButton</span>(<span class="params">prop: SideProps</span>): <span class="title">JSX</span>.<span class="title">Element</span> </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="类组件"><a href="#类组件" class="headerlink" title="类组件"></a>类组件</h3><p>我们可以定义类组件的类型。 然而，我们首先最好弄懂两个新的术语：<em>元素类的类型</em>和<em>元素实例的类型</em>。</p>
<p>现在有<code>&lt;Expr /&gt;</code>，<em>元素类的类型</em>为<code>Expr</code>的类型。 所以在上面的例子里，如果<code>MyComponent</code>是ES6的类，那么类类型就是类的构造函数和静态部分。 如果<code>MyComponent</code>是个工厂函数，类类型为这个函数。</p>
<p>一旦建立起了类类型，实例类型由类构造器或调用签名（如果存在的话）的返回值的联合构成。 再次说明，在ES6类的情况下，实例类型为这个类的实例的类型，并且如果是工厂函数，实例类型为这个函数返回值类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> MyComponent &#123;</span><br><span class="line">    render() &#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用构造签名</span></span><br><span class="line"><span class="keyword">var</span> myComponent = <span class="keyword">new</span> MyComponent();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 元素类的类型 =&gt; MyComponent</span></span><br><span class="line"><span class="comment">// 元素实例的类型 =&gt; &#123; render: () =&gt; void &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">MyFactoryFunction</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">    render: <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用调用签名</span></span><br><span class="line"><span class="keyword">var</span> myComponent = MyFactoryFunction();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 元素类的类型 =&gt; FactoryFunction</span></span><br><span class="line"><span class="comment">// 元素实例的类型 =&gt; &#123; render: () =&gt; void &#125;</span></span><br></pre></td></tr></table></figure>

<p>元素的实例类型很有趣，因为它必须赋值给<code>JSX.ElementClass</code>或抛出一个错误。 默认的<code>JSX.ElementClass</code>为<code>{}</code>，但是它可以被扩展用来限制JSX的类型以符合相应的接口。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">namespace</span> JSX &#123;</span><br><span class="line">    <span class="keyword">interface</span> ElementClass &#123;</span><br><span class="line">    render: <span class="built_in">any</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> MyComponent &#123;</span><br><span class="line">    render() &#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">MyFactoryFunction</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> &#123; render: <span class="function"><span class="params">()</span> =&gt;</span> &#123;&#125; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&lt;MyComponent /&gt;; <span class="comment">// 正确</span></span><br><span class="line">&lt;MyFactoryFunction /&gt;; <span class="comment">// 正确</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> NotAValidComponent &#123;&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">NotAValidFactoryFunction</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&lt;NotAValidComponent /&gt;; <span class="comment">// 错误</span></span><br><span class="line">&lt;NotAValidFactoryFunction /&gt;; <span class="comment">// 错误</span></span><br></pre></td></tr></table></figure>

<p>属性类型检查：</p>
<p>属性类型检查的第一步是确定<em>元素属性类型</em>。 这在固有元素和基于值的元素之间稍有不同。</p>
<p>对于固有元素，这是<code>JSX.IntrinsicElements</code>属性的类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">namespace</span> JSX &#123;</span><br><span class="line">    <span class="keyword">interface</span> IntrinsicElements &#123;</span><br><span class="line">    foo: &#123; bar?: <span class="built_in">boolean</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// `foo`的元素属性类型为`&#123;bar?: boolean&#125;`</span></span><br><span class="line">&lt;foo bar /&gt;;</span><br></pre></td></tr></table></figure>

<p>对于基于值的元素，就稍微复杂些。 它取决于先前确定的在元素实例类型上的某个属性的类型。 至于该使用哪个属性来确定类型取决于<code>JSX.ElementAttributesProperty</code>。 它应该使用单一的属性来定义。 这个属性名之后会被使用。 TypeScript 2.8，如果未指定<code>JSX.ElementAttributesProperty</code>，那么将使用类元素构造函数或SFC调用的第一个参数的类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">namespace</span> JSX &#123;</span><br><span class="line">    <span class="keyword">interface</span> ElementAttributesProperty &#123;</span><br><span class="line">    props; <span class="comment">// 指定用来使用的属性名</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> MyComponent &#123;</span><br><span class="line">    <span class="comment">// 在元素实例类型上指定属性</span></span><br><span class="line">    props: &#123;</span><br><span class="line">    foo?: <span class="built_in">string</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// `MyComponent`的元素属性类型为`&#123;foo?: string&#125;`</span></span><br><span class="line">&lt;MyComponent foo=<span class="string">"bar"</span> /&gt;</span><br></pre></td></tr></table></figure>

<p>元素属性类型用于的JSX里进行属性的类型检查。 支持可选属性和必须属性。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">namespace</span> JSX &#123;</span><br><span class="line">    <span class="keyword">interface</span> IntrinsicElements &#123;</span><br><span class="line">    foo: &#123; requiredProp: <span class="built_in">string</span>; optionalProp?: <span class="built_in">number</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&lt;foo requiredProp=<span class="string">"bar"</span> /&gt;; <span class="comment">// 正确</span></span><br><span class="line">&lt;foo requiredProp=<span class="string">"bar"</span> optionalProp=&#123;<span class="number">0</span>&#125; /&gt;; <span class="comment">// 正确</span></span><br><span class="line">&lt;foo /&gt;; <span class="comment">// 错误, 缺少 requiredProp</span></span><br><span class="line">&lt;foo requiredProp=&#123;<span class="number">0</span>&#125; /&gt;; <span class="comment">// 错误, requiredProp 应该是字符串</span></span><br><span class="line">&lt;foo requiredProp=<span class="string">"bar"</span> unknownProp /&gt;; <span class="comment">// 错误, unknownProp 不存在</span></span><br><span class="line">&lt;foo requiredProp=<span class="string">"bar"</span> some-unknown-prop /&gt;; <span class="comment">// 正确, `some-unknown-prop`不是个合法的标识符</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p>注意：如果一个属性名不是个合法的JS标识符（像<code>data-*</code>属性），并且它没出现在元素属性类型里时不会当做一个错误。</p>
</blockquote>
<p>另外，JSX还会使用<code>JSX.IntrinsicAttributes</code>接口来指定额外的属性，这些额外的属性通常不会被组件的props或arguments使用 - 比如React里的<code>key</code>。还有，<code>JSX.IntrinsicClassAttributes&lt;T&gt;</code>泛型类型也可以用来做同样的事情。这里的泛型参数表示类实例类型。在React里，它用来允许<code>Ref&lt;T&gt;</code>类型上的<code>ref</code>属性。通常来讲，这些接口上的所有属性都是可选的，除非你想要用户在每个JSX标签上都提供一些属性。</p>
<p>延展操作符也可以使用：</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> props = &#123; <span class="attr">requiredProp</span>: <span class="string">'bar'</span> &#125;;</span><br><span class="line">&lt;foo &#123;...props&#125; /&gt;; <span class="comment">// 正确</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> badProps = &#123;&#125;;</span><br><span class="line">&lt;foo &#123;...badProps&#125; /&gt;; <span class="comment">// 错误</span></span><br></pre></td></tr></table></figure>

<p>子孙类型检查：</p>
<p>从TypeScript 2.3开始，我们引入了<em>children</em>类型检查。<em>children</em>是<em>元素属性(attribute)类型</em>的一个特殊属性(property)，子<em>JSXExpression</em>将会被插入到属性里。 与使用<code>JSX.ElementAttributesProperty</code>来决定<em>props</em>名类似，我们可以利用<code>JSX.ElementChildrenAttribute</code>来决定<em>children</em>名。 <code>JSX.ElementChildrenAttribute</code>应该被声明在单一的属性(property)里。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">declare</span> <span class="keyword">namespace</span> JSX &#123;</span><br><span class="line">    <span class="keyword">interface</span> ElementChildrenAttribute &#123;</span><br><span class="line">    children: &#123;&#125;;  <span class="comment">// specify children name to use</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如不特殊指定子孙的类型，我们将使用<a href="https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react" target="_blank" rel="noopener">React typings</a>里的默认类型。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">&lt;div&gt;</span><br><span class="line">    &lt;h1&gt;Hello&lt;<span class="regexp">/h1&gt;</span></span><br><span class="line"><span class="regexp">&lt;/</span>div&gt;;</span><br><span class="line"></span><br><span class="line">&lt;div&gt;</span><br><span class="line">    &lt;h1&gt;Hello&lt;<span class="regexp">/h1&gt;</span></span><br><span class="line"><span class="regexp">    World</span></span><br><span class="line"><span class="regexp">&lt;/</span>div&gt;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> CustomComp = <span class="function">(<span class="params">props</span>) =&gt;</span> &lt;div&gt;props.children&lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">&lt;CustomComp&gt;</span></span><br><span class="line"><span class="regexp">    &lt;div&gt;Hello World&lt;/</span>div&gt;</span><br><span class="line">    &#123;<span class="string">"This is just a JS expression..."</span> + <span class="number">1000</span>&#125;</span><br><span class="line">&lt;<span class="regexp">/CustomComp&gt;</span></span><br><span class="line"><span class="regexp">interface PropsType &#123;</span></span><br><span class="line"><span class="regexp">    children: JSX.Element</span></span><br><span class="line"><span class="regexp">    name: string</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">class Component extends React.Component&lt;PropsType, &#123;&#125;&gt; &#123;</span></span><br><span class="line"><span class="regexp">    render() &#123;</span></span><br><span class="line"><span class="regexp">        return (</span></span><br><span class="line"><span class="regexp">            &lt;h2&gt;</span></span><br><span class="line"><span class="regexp">            &#123;this.props.children&#125;</span></span><br><span class="line"><span class="regexp">            &lt;/</span>h2&gt;</span><br><span class="line">        )</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// OK</span></span><br><span class="line">&lt;Component&gt;</span><br><span class="line">    &lt;h1&gt;Hello World&lt;<span class="regexp">/h1&gt;</span></span><br><span class="line"><span class="regexp">&lt;/</span>Component&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Error: children is of type JSX.Element not array of JSX.Element</span></span><br><span class="line">&lt;Component&gt;</span><br><span class="line">    &lt;h1&gt;Hello World&lt;<span class="regexp">/h1&gt;</span></span><br><span class="line"><span class="regexp">    &lt;h2&gt;Hello World&lt;/</span>h2&gt;</span><br><span class="line">&lt;<span class="regexp">/Component&gt;</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">/</span><span class="regexp">/ Error: children is of type JSX.Element not array of JSX.Element or string.</span></span><br><span class="line"><span class="regexp">&lt;Component&gt;</span></span><br><span class="line"><span class="regexp">    &lt;h1&gt;Hello&lt;/</span>h1&gt;</span><br><span class="line">    World</span><br><span class="line">&lt;<span class="regexp">/Component&gt;</span></span><br></pre></td></tr></table></figure>

<h3 id="JSX结果类型"><a href="#JSX结果类型" class="headerlink" title="JSX结果类型"></a>JSX结果类型</h3><p>默认地JSX表达式结果的类型为<code>any</code>。 你可以自定义这个类型，通过指定<code>JSX.Element</code>接口。 然而，不能够从接口里检索元素，属性或JSX的子元素的类型信息。 它是一个黑盒。</p>
<p>JSX允许你使用<code>{ }</code>标签来内嵌表达式。</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    &#123;['foo', 'bar'].map(i =&gt; <span class="tag">&lt;<span class="name">span</span>&gt;</span>&#123;i / 2&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span>)&#125;</span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br></pre></td></tr></table></figure>

<p>上面的代码产生一个错误，因为你不能用数字来除以一个字符串。 输出如下，若你使用了<code>preserve</code>选项：</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    &#123;['foo', 'bar'].map(function (i) &#123; return <span class="tag">&lt;<span class="name">span</span>&gt;</span>&#123;i / 2&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span>; &#125;)&#125;</span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br></pre></td></tr></table></figure>

<h3 id="React整合"><a href="#React整合" class="headerlink" title="React整合"></a>React整合</h3><p>要想一起使用JSX和React，你应该使用<a href="https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react" target="_blank" rel="noopener">React类型定义</a>。 这些类型声明定义了<code>JSX</code>合适命名空间来使用React。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// &lt;reference path="react.d.ts" /&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Props &#123;</span><br><span class="line">    foo: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> MyComponent <span class="keyword">extends</span> React.Component&lt;Props, &#123;&#125;&gt; &#123;</span><br><span class="line">    render() &#123;</span><br><span class="line">    <span class="keyword">return</span> &lt;span&gt;&#123;<span class="keyword">this</span>.props.foo&#125;&lt;<span class="regexp">/span&gt;</span></span><br><span class="line"><span class="regexp">    &#125;</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">&lt;MyComponent foo="bar" /</span>&gt;; <span class="comment">// 正确</span></span><br><span class="line">&lt;MyComponent foo=&#123;<span class="number">0</span>&#125; /&gt;; <span class="comment">// 错误</span></span><br></pre></td></tr></table></figure>

<h3 id="工厂函数"><a href="#工厂函数" class="headerlink" title="工厂函数"></a>工厂函数</h3><p><code>jsx: react</code>编译选项使用的工厂函数是可以配置的。可以使用<code>jsxFactory</code>命令行选项，或内联的<code>@jsx</code>注释指令在每个文件上设置。比如，给<code>createElement</code>设置<code>jsxFactory</code>，<code>&lt;div /&gt;</code>会使用<code>createElement(&quot;div&quot;)</code>来生成，而不是<code>React.createElement(&quot;div&quot;)</code>。</p>
<p>注释指令可以像下面这样使用（在TypeScript 2.8里）：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> preact = <span class="built_in">require</span>(<span class="string">"preact"</span>);</span><br><span class="line"><span class="comment">/* @jsx preact.h */</span></span><br><span class="line"><span class="keyword">const</span> x = &lt;div /&gt;;</span><br></pre></td></tr></table></figure>

<p>生成：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> preact = <span class="built_in">require</span>(<span class="string">"preact"</span>);</span><br><span class="line"><span class="keyword">const</span> x = preact.h(<span class="string">"div"</span>, <span class="literal">null</span>);</span><br></pre></td></tr></table></figure>

<p>工厂函数的选择同样会影响<code>JSX</code>命名空间的查找（类型检查）。如果工厂函数使用<code>React.createElement</code>定义（默认），编译器会先检查<code>React.JSX</code>，之后才检查全局的<code>JSX</code>。如果工厂函数定义为<code>h</code>，那么在检查全局的<code>JSX</code>之前先检查<code>h.JSX</code>。</p>
<h2 id="装饰器"><a href="#装饰器" class="headerlink" title="装饰器"></a>装饰器</h2><p>随着TypeScript和ES6里引入了类，在一些场景下我们需要额外的特性来支持标注或修改类及其成员。 装饰器（Decorators）为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式。 Javascript里的装饰器目前处在 <a href="https://github.com/tc39/proposal-decorators" target="_blank" rel="noopener">建议征集的第二阶段</a>，但在TypeScript里已做为一项实验性特性予以支持。</p>
<blockquote>
<p>注意  装饰器是一项实验性特性，在未来的版本中可能会发生改变。</p>
</blockquote>
<p>若要启用实验性的装饰器特性，你必须在命令行或<code>tsconfig.json</code>里启用<code>experimentalDecorators</code>编译器选项：</p>
<p><strong>命令行</strong>:</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tsc --target ES5 --experimentalDecorators</span><br></pre></td></tr></table></figure>

<p><strong>tsconfig.json</strong>:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">"compilerOptions"</span>: &#123;</span><br><span class="line">        <span class="attr">"target"</span>: <span class="string">"ES5"</span>,</span><br><span class="line">        <span class="attr">"experimentalDecorators"</span>: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><em>装饰器</em>是一种特殊类型的声明，它能够被附加到<a href="https://www.tslang.cn/docs/handbook/decorators.html#class-decorators" target="_blank" rel="noopener">类声明</a>，<a href="https://www.tslang.cn/docs/handbook/decorators.html#method-decorators" target="_blank" rel="noopener">方法</a>， <a href="https://www.tslang.cn/docs/handbook/decorators.html#accessor-decorators" target="_blank" rel="noopener">访问符</a>，<a href="https://www.tslang.cn/docs/handbook/decorators.html#property-decorators" target="_blank" rel="noopener">属性</a>或<a href="https://www.tslang.cn/docs/handbook/decorators.html#parameter-decorators" target="_blank" rel="noopener">参数</a>上。 装饰器使用 <code>@expression</code>这种形式，<code>expression</code>求值后必须为一个函数，它会在运行时被调用，被装饰的声明信息做为参数传入。</p>
<p>例如，有一个<code>@sealed</code>装饰器，我们会这样定义<code>sealed</code>函数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sealed</span>(<span class="params">target</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// do something with "target" ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>注意  后面<a href="https://www.tslang.cn/docs/handbook/decorators.html#class-decorators" target="_blank" rel="noopener">类装饰器</a>小节里有一个更加详细的例子。</p>
</blockquote>
<h3 id="装饰器工厂"><a href="#装饰器工厂" class="headerlink" title="装饰器工厂"></a>装饰器工厂</h3><p>如果我们要定制一个修饰器如何应用到一个声明上，我们得写一个装饰器工厂函数。 <em>装饰器工厂</em>就是一个简单的函数，它返回一个表达式，以供装饰器在运行时调用。</p>
<p>我们可以通过下面的方式来写一个装饰器工厂函数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">color</span>(<span class="params">value: <span class="built_in">string</span></span>) </span>&#123; <span class="comment">// 这是一个装饰器工厂</span></span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target</span>) </span>&#123; <span class="comment">//  这是装饰器</span></span><br><span class="line">        <span class="comment">// do something with "target" and "value"...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>注意  下面<a href="https://www.tslang.cn/docs/handbook/decorators.html#method-decorators" target="_blank" rel="noopener">方法装饰器</a>小节里有一个更加详细的例子。</p>
</blockquote>
<p>装饰器组合</p>
<p>多个装饰器可以同时应用到一个声明上，就像下面的示例：</p>
<ul>
<li>书写在同一行上：</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@f</span> <span class="meta">@g</span> x</span><br></pre></td></tr></table></figure>

<ul>
<li>书写在多行上：</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@f</span></span><br><span class="line"><span class="meta">@g</span></span><br><span class="line">x</span><br></pre></td></tr></table></figure>

<p>当多个装饰器应用于一个声明上，它们求值方式与<a href="http://en.wikipedia.org/wiki/Function_composition" target="_blank" rel="noopener">复合函数</a>相似。在这个模型下，当复合<em>f</em>和<em>g</em>时，复合的结果(<em>f</em> ∘ <em>g</em>)(<em>x</em>)等同于<em>f</em>(<em>g</em>(<em>x</em>))。</p>
<p>同样的，在TypeScript里，当多个装饰器应用在一个声明上时会进行如下步骤的操作：</p>
<ol>
<li>由上至下依次对装饰器表达式求值。</li>
<li>求值的结果会被当作函数，由下至上依次调用。</li>
</ol>
<p>如果我们使用<a href="https://www.tslang.cn/docs/handbook/decorators.html#decorator-factories" target="_blank" rel="noopener">装饰器工厂</a>的话，可以通过下面的例子来观察它们求值的顺序：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"f(): evaluated"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target, propertyKey: <span class="built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"f(): called"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">g</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"g(): evaluated"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target, propertyKey: <span class="built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"g(): called"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> C &#123;</span><br><span class="line">    <span class="meta">@f</span>()</span><br><span class="line">    <span class="meta">@g</span>()</span><br><span class="line">    method() &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在控制台里会打印出如下结果：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">f(): evaluated</span><br><span class="line">g(): evaluated</span><br><span class="line">g(): called</span><br><span class="line">f(): called</span><br></pre></td></tr></table></figure>

<p>装饰器求值</p>
<p>类中不同声明上的装饰器将按以下规定的顺序应用：</p>
<ol>
<li><em>参数装饰器</em>，然后依次是<em>方法装饰器</em>，<em>访问符装饰器</em>，或<em>属性装饰器</em>应用到每个实例成员。</li>
<li><em>参数装饰器</em>，然后依次是<em>方法装饰器</em>，<em>访问符装饰器</em>，或<em>属性装饰器</em>应用到每个静态成员。</li>
<li><em>参数装饰器</em>应用到构造函数。</li>
<li><em>类装饰器</em>应用到类。</li>
</ol>
<p>类装饰器</p>
<p><em>类装饰器</em>在类声明之前被声明（紧靠着类声明）。 类装饰器应用于类构造函数，可以用来监视，修改或替换类定义。 类装饰器不能用在声明文件中( <code>.d.ts</code>)，也不能用在任何外部上下文中（比如<code>declare</code>的类）。</p>
<p>类装饰器表达式会在运行时当作函数被调用，类的构造函数作为其唯一的参数。</p>
<p>如果类装饰器返回一个值，它会使用提供的构造函数来替换类的声明。</p>
<blockquote>
<p>注意 如果你要返回一个新的构造函数，你必须注意处理好原来的原型链。 在运行时的装饰器调用逻辑中 <em>不会</em>为你做这些。</p>
</blockquote>
<p>下面是使用类装饰器(<code>@sealed</code>)的例子，应用在<code>Greeter</code>类：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@sealed</span></span><br><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    greeting: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">message: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.greeting = message;</span><br><span class="line">    &#125;</span><br><span class="line">    greet() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Hello, "</span> + <span class="keyword">this</span>.greeting;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以这样定义<code>@sealed</code>装饰器：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sealed</span>(<span class="params">constructor: <span class="built_in">Function</span></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">Object</span>.seal(<span class="keyword">constructor</span>);</span><br><span class="line">    <span class="built_in">Object</span>.seal(<span class="keyword">constructor</span>.prototype);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当<code>@sealed</code>被执行的时候，它将密封此类的构造函数和原型。(注：参见<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/seal" target="_blank" rel="noopener">Object.seal</a>)</p>
<p>下面是一个重载构造函数的例子。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">classDecorator</span>&lt;<span class="title">T</span> <span class="title">extends</span> </span>&#123;<span class="keyword">new</span>(...args:<span class="built_in">any</span>[]):&#123;&#125;&#125;&gt;(<span class="keyword">constructor</span>:T) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">class</span> <span class="keyword">extends</span> <span class="keyword">constructor</span> &#123;</span><br><span class="line">        newProperty = <span class="string">"new property"</span>;</span><br><span class="line">        hello = <span class="string">"override"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@classDecorator</span></span><br><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    property = <span class="string">"property"</span>;</span><br><span class="line">    hello: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">m: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.hello = m;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">new</span> Greeter(<span class="string">"world"</span>));</span><br></pre></td></tr></table></figure>

<p>方法装饰器：</p>
<p><em>方法装饰器</em>声明在一个方法的声明之前（紧靠着方法声明）。 它会被应用到方法的 <em>属性描述符</em>上，可以用来监视，修改或者替换方法定义。 方法装饰器不能用在声明文件( <code>.d.ts</code>)，重载或者任何外部上下文（比如<code>declare</code>的类）中。</p>
<p>方法装饰器表达式会在运行时当作函数被调用，传入下列3个参数：</p>
<ol>
<li>对于静态成员来说是类的构造函数，对于实例成员是类的原型对象。</li>
<li>成员的名字。</li>
<li>成员的<em>属性描述符</em>。</li>
</ol>
<blockquote>
<p>注意  如果代码输出目标版本小于<code>ES5</code>，<em>属性描述符</em>将会是<code>undefined</code>。</p>
</blockquote>
<p>如果方法装饰器返回一个值，它会被用作方法的<em>属性描述符</em>。</p>
<blockquote>
<p>注意  如果代码输出目标版本小于<code>ES5</code>返回值会被忽略。</p>
</blockquote>
<p>下面是一个方法装饰器（<code>@enumerable</code>）的例子，应用于<code>Greeter</code>类的方法上：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    greeting: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">message: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.greeting = message;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@enumerable</span>(<span class="literal">false</span>)</span><br><span class="line">    greet() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Hello, "</span> + <span class="keyword">this</span>.greeting;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以用下面的函数声明来定义<code>@enumerable</code>装饰器：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">enumerable</span>(<span class="params">value: <span class="built_in">boolean</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>&#123;</span><br><span class="line">        descriptor.enumerable = value;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里的<code>@enumerable(false)</code>是一个<a href="https://www.tslang.cn/docs/handbook/decorators.html#decorator-factories" target="_blank" rel="noopener">装饰器工厂</a>。 当装饰器 <code>@enumerable(false)</code>被调用时，它会修改属性描述符的<code>enumerable</code>属性。</p>
<p>访问器装饰器：</p>
<p><em>访问器装饰器</em>声明在一个访问器的声明之前（紧靠着访问器声明）。 访问器装饰器应用于访问器的 <em>属性描述符</em>并且可以用来监视，修改或替换一个访问器的定义。 访问器装饰器不能用在声明文件中（.d.ts），或者任何外部上下文（比如 <code>declare</code>的类）里。</p>
<blockquote>
<p>注意  TypeScript不允许同时装饰一个成员的<code>get</code>和<code>set</code>访问器。取而代之的是，一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为，在装饰器应用于一个<em>属性描述符</em>时，它联合了<code>get</code>和<code>set</code>访问器，而不是分开声明的。</p>
</blockquote>
<p>访问器装饰器表达式会在运行时当作函数被调用，传入下列3个参数：</p>
<ol>
<li>对于静态成员来说是类的构造函数，对于实例成员是类的原型对象。</li>
<li>成员的名字。</li>
<li>成员的<em>属性描述符</em>。</li>
</ol>
<blockquote>
<p>注意  如果代码输出目标版本小于<code>ES5</code>，<em>Property Descriptor</em>将会是<code>undefined</code>。</p>
</blockquote>
<p>如果访问器装饰器返回一个值，它会被用作方法的<em>属性描述符</em>。</p>
<blockquote>
<p>注意  如果代码输出目标版本小于<code>ES5</code>返回值会被忽略。</p>
</blockquote>
<p>下面是使用了访问器装饰器（<code>@configurable</code>）的例子，应用于<code>Point</code>类的成员上：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Point &#123;</span><br><span class="line">    <span class="keyword">private</span> _x: <span class="built_in">number</span>;</span><br><span class="line">    <span class="keyword">private</span> _y: <span class="built_in">number</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">x: <span class="built_in">number</span>, y: <span class="built_in">number</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>._x = x;</span><br><span class="line">        <span class="keyword">this</span>._y = y;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@configurable</span>(<span class="literal">false</span>)</span><br><span class="line">    <span class="keyword">get</span> x() &#123; <span class="keyword">return</span> <span class="keyword">this</span>._x; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@configurable</span>(<span class="literal">false</span>)</span><br><span class="line">    <span class="keyword">get</span> y() &#123; <span class="keyword">return</span> <span class="keyword">this</span>._y; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以通过如下函数声明来定义<code>@configurable</code>装饰器：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">configurable</span>(<span class="params">value: <span class="built_in">boolean</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>&#123;</span><br><span class="line">        descriptor.configurable = value;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>属性装饰器：</p>
<p><em>属性装饰器</em>声明在一个属性声明之前（紧靠着属性声明）。 属性装饰器不能用在声明文件中（.d.ts），或者任何外部上下文（比如 <code>declare</code>的类）里。</p>
<p>属性装饰器表达式会在运行时当作函数被调用，传入下列2个参数：</p>
<ol>
<li>对于静态成员来说是类的构造函数，对于实例成员是类的原型对象。</li>
<li>成员的名字。</li>
</ol>
<blockquote>
<p>注意  <em>属性描述符</em>不会做为参数传入属性装饰器，这与TypeScript是如何初始化属性装饰器的有关。 因为目前没有办法在定义一个原型对象的成员时描述一个实例属性，并且没办法监视或修改一个属性的初始化方法。返回值也会被忽略。因此，属性描述符只能用来监视类中是否声明了某个名字的属性。</p>
</blockquote>
<p>我们可以用它来记录这个属性的元数据，如下例所示：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    <span class="meta">@format</span>(<span class="string">"Hello, %s"</span>)</span><br><span class="line">    greeting: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">message: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.greeting = message;</span><br><span class="line">    &#125;</span><br><span class="line">    greet() &#123;</span><br><span class="line">        <span class="keyword">let</span> formatString = getFormat(<span class="keyword">this</span>, <span class="string">"greeting"</span>);</span><br><span class="line">        <span class="keyword">return</span> formatString.replace(<span class="string">"%s"</span>, <span class="keyword">this</span>.greeting);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后定义<code>@format</code>装饰器和<code>getFormat</code>函数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"reflect-metadata"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> formatMetadataKey = Symbol(<span class="string">"format"</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">format</span>(<span class="params">formatString: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Reflect.metadata(formatMetadataKey, formatString);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getFormat</span>(<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Reflect.getMetadata(formatMetadataKey, target, propertyKey);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这个<code>@format(&quot;Hello, %s&quot;)</code>装饰器是个 <a href="https://www.tslang.cn/docs/handbook/decorators.html#decorator-factories" target="_blank" rel="noopener">装饰器工厂</a>。 当 <code>@format(&quot;Hello, %s&quot;)</code>被调用时，它添加一条这个属性的元数据，通过<code>reflect-metadata</code>库里的<code>Reflect.metadata</code>函数。 当 <code>getFormat</code>被调用时，它读取格式的元数据。</p>
<blockquote>
<p>注意  这个例子需要使用<code>reflect-metadata</code>库。 查看 <a href="https://www.tslang.cn/docs/handbook/decorators.html#metadata" target="_blank" rel="noopener">元数据</a>了解<code>reflect-metadata</code>库更详细的信息。</p>
</blockquote>
<p>参数装饰器：</p>
<p><em>参数装饰器</em>声明在一个参数声明之前（紧靠着参数声明）。 参数装饰器应用于类构造函数或方法声明。 参数装饰器不能用在声明文件（.d.ts），重载或其它外部上下文（比如 <code>declare</code>的类）里。</p>
<p>参数装饰器表达式会在运行时当作函数被调用，传入下列3个参数：</p>
<ol>
<li>对于静态成员来说是类的构造函数，对于实例成员是类的原型对象。</li>
<li>成员的名字。</li>
<li>参数在函数参数列表中的索引。</li>
</ol>
<blockquote>
<p>注意  参数装饰器只能用来监视一个方法的参数是否被传入。</p>
</blockquote>
<p>参数装饰器的返回值会被忽略。</p>
<p>下例定义了参数装饰器（<code>@required</code>）并应用于<code>Greeter</code>类方法的一个参数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    greeting: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">message: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.greeting = message;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@validate</span></span><br><span class="line">    greet(<span class="meta">@required</span> name: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Hello "</span> + name + <span class="string">", "</span> + <span class="keyword">this</span>.greeting;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后我们使用下面的函数定义 <code>@required</code> 和 <code>@validate</code> 装饰器：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"reflect-metadata"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> requiredMetadataKey = Symbol(<span class="string">"required"</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">required</span>(<span class="params">target: <span class="built_in">Object</span>, propertyKey: <span class="built_in">string</span> | symbol, parameterIndex: <span class="built_in">number</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> existingRequiredParameters: <span class="built_in">number</span>[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];</span><br><span class="line">    existingRequiredParameters.push(parameterIndex);</span><br><span class="line">    Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">validate</span>(<span class="params">target: <span class="built_in">any</span>, propertyName: <span class="built_in">string</span>, descriptor: TypedPropertyDescriptor&lt;<span class="built_in">Function</span>&gt;</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> method = descriptor.value;</span><br><span class="line">    descriptor.value = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">let</span> requiredParameters: <span class="built_in">number</span>[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);</span><br><span class="line">        <span class="keyword">if</span> (requiredParameters) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">let</span> parameterIndex of requiredParameters) &#123;</span><br><span class="line">                <span class="keyword">if</span> (parameterIndex &gt;= <span class="built_in">arguments</span>.length || <span class="built_in">arguments</span>[parameterIndex] === <span class="literal">undefined</span>) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"Missing required argument."</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> method.apply(<span class="keyword">this</span>, <span class="built_in">arguments</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>@required</code>装饰器添加了元数据实体把参数标记为必需的。 <code>@validate</code>装饰器把<code>greet</code>方法包裹在一个函数里在调用原先的函数前验证函数参数。</p>
<blockquote>
<p>注意  这个例子使用了<code>reflect-metadata</code>库。 查看 <a href="https://www.tslang.cn/docs/handbook/decorators.html#metadata" target="_blank" rel="noopener">元数据</a>了解<code>reflect-metadata</code>库的更多信息。</p>
</blockquote>
<p>元数据：</p>
<p>一些例子使用了<code>reflect-metadata</code>库来支持<a href="https://github.com/rbuckton/ReflectDecorators" target="_blank" rel="noopener">实验性的metadata API</a>。 这个库还不是ECMAScript (JavaScript)标准的一部分。 然而，当装饰器被ECMAScript官方标准采纳后，这些扩展也将被推荐给ECMAScript以采纳。</p>
<p>你可以通过npm安装这个库：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i reflect-metadata --save</span><br></pre></td></tr></table></figure>

<p>TypeScript支持为带有装饰器的声明生成元数据。 你需要在命令行或 <code>tsconfig.json</code>里启用<code>emitDecoratorMetadata</code>编译器选项。</p>
<p><strong>Command Line</strong>:</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata</span><br></pre></td></tr></table></figure>

<p><strong>tsconfig.json</strong>:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">"compilerOptions"</span>: &#123;</span><br><span class="line">        <span class="attr">"target"</span>: <span class="string">"ES5"</span>,</span><br><span class="line">        <span class="attr">"experimentalDecorators"</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="attr">"emitDecoratorMetadata"</span>: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当启用后，只要<code>reflect-metadata</code>库被引入了，设计阶段添加的类型信息可以在运行时使用。</p>
<p>如下例所示：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"reflect-metadata"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Point &#123;</span><br><span class="line">    x: <span class="built_in">number</span>;</span><br><span class="line">    y: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Line &#123;</span><br><span class="line">    <span class="keyword">private</span> _p0: Point;</span><br><span class="line">    <span class="keyword">private</span> _p1: Point;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@validate</span></span><br><span class="line">    <span class="keyword">set</span> p0(value: Point) &#123; <span class="keyword">this</span>._p0 = value; &#125;</span><br><span class="line">    <span class="keyword">get</span> p0() &#123; <span class="keyword">return</span> <span class="keyword">this</span>._p0; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@validate</span></span><br><span class="line">    <span class="keyword">set</span> p1(value: Point) &#123; <span class="keyword">this</span>._p1 = value; &#125;</span><br><span class="line">    <span class="keyword">get</span> p1() &#123; <span class="keyword">return</span> <span class="keyword">this</span>._p1; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">validate</span>&lt;<span class="title">T</span>&gt;(<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span>, descriptor: TypedPropertyDescriptor&lt;T&gt;</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">set</span> = descriptor.set;</span><br><span class="line">    descriptor.set = <span class="function"><span class="keyword">function</span> (<span class="params">value: T</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="keyword">type</span> = Reflect.getMetadata(<span class="string">"design:type"</span>, target, propertyKey);</span><br><span class="line">        <span class="keyword">if</span> (!(value <span class="keyword">instanceof</span> <span class="keyword">type</span>)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">"Invalid type."</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">set</span>(value);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>TypeScript编译器可以通过<code>@Reflect.metadata</code>装饰器注入设计阶段的类型信息。 你可以认为它相当于下面的TypeScript：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Line &#123;</span><br><span class="line">    <span class="keyword">private</span> _p0: Point;</span><br><span class="line">    <span class="keyword">private</span> _p1: Point;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@validate</span></span><br><span class="line">    <span class="meta">@Reflect</span>.metadata(<span class="string">"design:type"</span>, Point)</span><br><span class="line">    <span class="keyword">set</span> p0(value: Point) &#123; <span class="keyword">this</span>._p0 = value; &#125;</span><br><span class="line">    <span class="keyword">get</span> p0() &#123; <span class="keyword">return</span> <span class="keyword">this</span>._p0; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@validate</span></span><br><span class="line">    <span class="meta">@Reflect</span>.metadata(<span class="string">"design:type"</span>, Point)</span><br><span class="line">    <span class="keyword">set</span> p1(value: Point) &#123; <span class="keyword">this</span>._p1 = value; &#125;</span><br><span class="line">    <span class="keyword">get</span> p1() &#123; <span class="keyword">return</span> <span class="keyword">this</span>._p1; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>注意  装饰器元数据是个实验性的特性并且可能在以后的版本中发生破坏性的改变（breaking changes）。</p>
</blockquote>
<h3 id="装饰器组合"><a href="#装饰器组合" class="headerlink" title="装饰器组合"></a>装饰器组合</h3><p>多个装饰器可以同时应用到一个声明上，就像下面的示例：</p>
<ul>
<li>书写在同一行上：</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@f</span> <span class="meta">@g</span> x</span><br></pre></td></tr></table></figure>

<ul>
<li>书写在多行上：</li>
</ul>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@f</span></span><br><span class="line"><span class="meta">@g</span></span><br><span class="line">x</span><br></pre></td></tr></table></figure>

<p>当多个装饰器应用于一个声明上，它们求值方式与<a href="http://en.wikipedia.org/wiki/Function_composition" target="_blank" rel="noopener">复合函数</a>相似。在这个模型下，当复合<em>f</em>和<em>g</em>时，复合的结果(<em>f</em> ∘ <em>g</em>)(<em>x</em>)等同于<em>f</em>(<em>g</em>(<em>x</em>))。</p>
<p>同样的，在TypeScript里，当多个装饰器应用在一个声明上时会进行如下步骤的操作：</p>
<ol>
<li>由上至下依次对装饰器表达式求值。</li>
<li>求值的结果会被当作函数，由下至上依次调用。</li>
</ol>
<p>如果我们使用<a href="https://www.tslang.cn/docs/handbook/decorators.html#decorator-factories" target="_blank" rel="noopener">装饰器工厂</a>的话，可以通过下面的例子来观察它们求值的顺序：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"f(): evaluated"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target, propertyKey: <span class="built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"f(): called"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">g</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"g(): evaluated"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target, propertyKey: <span class="built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"g(): called"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> C &#123;</span><br><span class="line">    <span class="meta">@f</span>()</span><br><span class="line">    <span class="meta">@g</span>()</span><br><span class="line">    method() &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在控制台里会打印出如下结果：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">f(): evaluated</span><br><span class="line">g(): evaluated</span><br><span class="line">g(): called</span><br><span class="line">f(): called</span><br></pre></td></tr></table></figure>

<h3 id="装饰器求值"><a href="#装饰器求值" class="headerlink" title="装饰器求值"></a>装饰器求值</h3><p>类中不同声明上的装饰器将按以下规定的顺序应用：</p>
<ol>
<li><em>参数装饰器</em>，然后依次是<em>方法装饰器</em>，<em>访问符装饰器</em>，或<em>属性装饰器</em>应用到每个实例成员。</li>
<li><em>参数装饰器</em>，然后依次是<em>方法装饰器</em>，<em>访问符装饰器</em>，或<em>属性装饰器</em>应用到每个静态成员。</li>
<li><em>参数装饰器</em>应用到构造函数。</li>
<li><em>类装饰器</em>应用到类。</li>
</ol>
<p>类装饰器：</p>
<p><em>类装饰器</em>在类声明之前被声明（紧靠着类声明）。 类装饰器应用于类构造函数，可以用来监视，修改或替换类定义。 类装饰器不能用在声明文件中( <code>.d.ts</code>)，也不能用在任何外部上下文中（比如<code>declare</code>的类）。</p>
<p>类装饰器表达式会在运行时当作函数被调用，类的构造函数作为其唯一的参数。</p>
<p>如果类装饰器返回一个值，它会使用提供的构造函数来替换类的声明。</p>
<blockquote>
<p>注意 如果你要返回一个新的构造函数，你必须注意处理好原来的原型链。 在运行时的装饰器调用逻辑中 <em>不会</em>为你做这些。</p>
</blockquote>
<p>下面是使用类装饰器(<code>@sealed</code>)的例子，应用在<code>Greeter</code>类：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@sealed</span></span><br><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    greeting: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">message: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.greeting = message;</span><br><span class="line">    &#125;</span><br><span class="line">    greet() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Hello, "</span> + <span class="keyword">this</span>.greeting;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以这样定义<code>@sealed</code>装饰器：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sealed</span>(<span class="params">constructor: <span class="built_in">Function</span></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">Object</span>.seal(<span class="keyword">constructor</span>);</span><br><span class="line">    <span class="built_in">Object</span>.seal(<span class="keyword">constructor</span>.prototype);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当<code>@sealed</code>被执行的时候，它将密封此类的构造函数和原型。(注：参见<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/seal" target="_blank" rel="noopener">Object.seal</a>)</p>
<p>下面是一个重载构造函数的例子。</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">classDecorator</span>&lt;<span class="title">T</span> <span class="title">extends</span> </span>&#123;<span class="keyword">new</span>(...args:<span class="built_in">any</span>[]):&#123;&#125;&#125;&gt;(<span class="keyword">constructor</span>:T) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">class</span> <span class="keyword">extends</span> <span class="keyword">constructor</span> &#123;</span><br><span class="line">        newProperty = <span class="string">"new property"</span>;</span><br><span class="line">        hello = <span class="string">"override"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@classDecorator</span></span><br><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    property = <span class="string">"property"</span>;</span><br><span class="line">    hello: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">m: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.hello = m;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">new</span> Greeter(<span class="string">"world"</span>));</span><br></pre></td></tr></table></figure>

<p>方法装饰器：</p>
<p><em>方法装饰器</em>声明在一个方法的声明之前（紧靠着方法声明）。 它会被应用到方法的 <em>属性描述符</em>上，可以用来监视，修改或者替换方法定义。 方法装饰器不能用在声明文件( <code>.d.ts</code>)，重载或者任何外部上下文（比如<code>declare</code>的类）中。</p>
<p>方法装饰器表达式会在运行时当作函数被调用，传入下列3个参数：</p>
<ol>
<li>对于静态成员来说是类的构造函数，对于实例成员是类的原型对象。</li>
<li>成员的名字。</li>
<li>成员的<em>属性描述符</em>。</li>
</ol>
<blockquote>
<p>注意  如果代码输出目标版本小于<code>ES5</code>，<em>属性描述符</em>将会是<code>undefined</code>。</p>
</blockquote>
<p>如果方法装饰器返回一个值，它会被用作方法的<em>属性描述符</em>。</p>
<blockquote>
<p>注意  如果代码输出目标版本小于<code>ES5</code>返回值会被忽略。</p>
</blockquote>
<p>下面是一个方法装饰器（<code>@enumerable</code>）的例子，应用于<code>Greeter</code>类的方法上：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    greeting: <span class="built_in">string</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">message: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.greeting = message;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@enumerable</span>(<span class="literal">false</span>)</span><br><span class="line">    greet() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Hello, "</span> + <span class="keyword">this</span>.greeting;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以用下面的函数声明来定义<code>@enumerable</code>装饰器：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">enumerable</span>(<span class="params">value: <span class="built_in">boolean</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>&#123;</span><br><span class="line">        descriptor.enumerable = value;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里的<code>@enumerable(false)</code>是一个<a href="https://www.tslang.cn/docs/handbook/decorators.html#decorator-factories" target="_blank" rel="noopener">装饰器工厂</a>。 当装饰器 <code>@enumerable(false)</code>被调用时，它会修改属性描述符的<code>enumerable</code>属性。</p>
<p>访问器装饰器：</p>
<p><em>访问器装饰器</em>声明在一个访问器的声明之前（紧靠着访问器声明）。 访问器装饰器应用于访问器的 <em>属性描述符</em>并且可以用来监视，修改或替换一个访问器的定义。 访问器装饰器不能用在声明文件中（.d.ts），或者任何外部上下文（比如 <code>declare</code>的类）里。</p>
<blockquote>
<p>注意  TypeScript不允许同时装饰一个成员的<code>get</code>和<code>set</code>访问器。取而代之的是，一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为，在装饰器应用于一个<em>属性描述符</em>时，它联合了<code>get</code>和<code>set</code>访问器，而不是分开声明的。</p>
</blockquote>
<p>访问器装饰器表达式会在运行时当作函数被调用，传入下列3个参数：</p>
<ol>
<li>对于静态成员来说是类的构造函数，对于实例成员是类的原型对象。</li>
<li>成员的名字。</li>
<li>成员的<em>属性描述符</em>。</li>
</ol>
<blockquote>
<p>注意  如果代码输出目标版本小于<code>ES5</code>，<em>Property Descriptor</em>将会是<code>undefined</code>。</p>
</blockquote>
<p>如果访问器装饰器返回一个值，它会被用作方法的<em>属性描述符</em>。</p>
<blockquote>
<p>注意  如果代码输出目标版本小于<code>ES5</code>返回值会被忽略。</p>
</blockquote>
<p>下面是使用了访问器装饰器（<code>@configurable</code>）的例子，应用于<code>Point</code>类的成员上：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Point &#123;</span><br><span class="line">    <span class="keyword">private</span> _x: <span class="built_in">number</span>;</span><br><span class="line">    <span class="keyword">private</span> _y: <span class="built_in">number</span>;</span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">x: <span class="built_in">number</span>, y: <span class="built_in">number</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>._x = x;</span><br><span class="line">        <span class="keyword">this</span>._y = y;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@configurable</span>(<span class="literal">false</span>)</span><br><span class="line">    <span class="keyword">get</span> x() &#123; <span class="keyword">return</span> <span class="keyword">this</span>._x; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@configurable</span>(<span class="literal">false</span>)</span><br><span class="line">    <span class="keyword">get</span> y() &#123; <span class="keyword">return</span> <span class="keyword">this</span>._y; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以通过如下函数声明来定义<code>@configurable</code>装饰器：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">configurable</span>(<span class="params">value: <span class="built_in">boolean</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span>, descriptor: PropertyDescriptor</span>) </span>&#123;</span><br><span class="line">        descriptor.configurable = value;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>属性装饰器：</p>
<p><em>属性装饰器</em>声明在一个属性声明之前（紧靠着属性声明）。 属性装饰器不能用在声明文件中（.d.ts），或者任何外部上下文（比如 <code>declare</code>的类）里。</p>
<p>属性装饰器表达式会在运行时当作函数被调用，传入下列2个参数：</p>
<ol>
<li>对于静态成员来说是类的构造函数，对于实例成员是类的原型对象。</li>
<li>成员的名字。</li>
</ol>
<blockquote>
<p>注意  <em>属性描述符</em>不会做为参数传入属性装饰器，这与TypeScript是如何初始化属性装饰器的有关。 因为目前没有办法在定义一个原型对象的成员时描述一个实例属性，并且没办法监视或修改一个属性的初始化方法。返回值也会被忽略。因此，属性描述符只能用来监视类中是否声明了某个名字的属性。</p>
</blockquote>
<p>我们可以用它来记录这个属性的元数据，如下例所示：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    <span class="meta">@format</span>(<span class="string">"Hello, %s"</span>)</span><br><span class="line">    greeting: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">message: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.greeting = message;</span><br><span class="line">    &#125;</span><br><span class="line">    greet() &#123;</span><br><span class="line">        <span class="keyword">let</span> formatString = getFormat(<span class="keyword">this</span>, <span class="string">"greeting"</span>);</span><br><span class="line">        <span class="keyword">return</span> formatString.replace(<span class="string">"%s"</span>, <span class="keyword">this</span>.greeting);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后定义<code>@format</code>装饰器和<code>getFormat</code>函数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"reflect-metadata"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> formatMetadataKey = Symbol(<span class="string">"format"</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">format</span>(<span class="params">formatString: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Reflect.metadata(formatMetadataKey, formatString);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getFormat</span>(<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> Reflect.getMetadata(formatMetadataKey, target, propertyKey);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这个<code>@format(&quot;Hello, %s&quot;)</code>装饰器是个 <a href="https://www.tslang.cn/docs/handbook/decorators.html#decorator-factories" target="_blank" rel="noopener">装饰器工厂</a>。 当 <code>@format(&quot;Hello, %s&quot;)</code>被调用时，它添加一条这个属性的元数据，通过<code>reflect-metadata</code>库里的<code>Reflect.metadata</code>函数。 当 <code>getFormat</code>被调用时，它读取格式的元数据。</p>
<blockquote>
<p>注意  这个例子需要使用<code>reflect-metadata</code>库。 查看 <a href="https://www.tslang.cn/docs/handbook/decorators.html#metadata" target="_blank" rel="noopener">元数据</a>了解<code>reflect-metadata</code>库更详细的信息。</p>
</blockquote>
<p>参数装饰器：</p>
<p><em>参数装饰器</em>声明在一个参数声明之前（紧靠着参数声明）。 参数装饰器应用于类构造函数或方法声明。 参数装饰器不能用在声明文件（.d.ts），重载或其它外部上下文（比如 <code>declare</code>的类）里。</p>
<p>参数装饰器表达式会在运行时当作函数被调用，传入下列3个参数：</p>
<ol>
<li>对于静态成员来说是类的构造函数，对于实例成员是类的原型对象。</li>
<li>成员的名字。</li>
<li>参数在函数参数列表中的索引。</li>
</ol>
<blockquote>
<p>注意  参数装饰器只能用来监视一个方法的参数是否被传入。</p>
</blockquote>
<p>参数装饰器的返回值会被忽略。</p>
<p>下例定义了参数装饰器（<code>@required</code>）并应用于<code>Greeter</code>类方法的一个参数：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Greeter &#123;</span><br><span class="line">    greeting: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">constructor</span>(<span class="params">message: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">        <span class="keyword">this</span>.greeting = message;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@validate</span></span><br><span class="line">    greet(<span class="meta">@required</span> name: <span class="built_in">string</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Hello "</span> + name + <span class="string">", "</span> + <span class="keyword">this</span>.greeting;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后我们使用下面的函数定义 <code>@required</code> 和 <code>@validate</code> 装饰器：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"reflect-metadata"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> requiredMetadataKey = Symbol(<span class="string">"required"</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">required</span>(<span class="params">target: <span class="built_in">Object</span>, propertyKey: <span class="built_in">string</span> | symbol, parameterIndex: <span class="built_in">number</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> existingRequiredParameters: <span class="built_in">number</span>[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];</span><br><span class="line">    existingRequiredParameters.push(parameterIndex);</span><br><span class="line">    Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">validate</span>(<span class="params">target: <span class="built_in">any</span>, propertyName: <span class="built_in">string</span>, descriptor: TypedPropertyDescriptor&lt;<span class="built_in">Function</span>&gt;</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> method = descriptor.value;</span><br><span class="line">    descriptor.value = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">let</span> requiredParameters: <span class="built_in">number</span>[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);</span><br><span class="line">        <span class="keyword">if</span> (requiredParameters) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">let</span> parameterIndex of requiredParameters) &#123;</span><br><span class="line">                <span class="keyword">if</span> (parameterIndex &gt;= <span class="built_in">arguments</span>.length || <span class="built_in">arguments</span>[parameterIndex] === <span class="literal">undefined</span>) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"Missing required argument."</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> method.apply(<span class="keyword">this</span>, <span class="built_in">arguments</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>@required</code>装饰器添加了元数据实体把参数标记为必需的。 <code>@validate</code>装饰器把<code>greet</code>方法包裹在一个函数里在调用原先的函数前验证函数参数。</p>
<blockquote>
<p>注意  这个例子使用了<code>reflect-metadata</code>库。 查看 <a href="https://www.tslang.cn/docs/handbook/decorators.html#metadata" target="_blank" rel="noopener">元数据</a>了解<code>reflect-metadata</code>库的更多信息。</p>
</blockquote>
<p>元数据：</p>
<p>一些例子使用了<code>reflect-metadata</code>库来支持<a href="https://github.com/rbuckton/ReflectDecorators" target="_blank" rel="noopener">实验性的metadata API</a>。 这个库还不是ECMAScript (JavaScript)标准的一部分。 然而，当装饰器被ECMAScript官方标准采纳后，这些扩展也将被推荐给ECMAScript以采纳。</p>
<p>你可以通过npm安装这个库：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i reflect-metadata --save</span><br></pre></td></tr></table></figure>

<p>TypeScript支持为带有装饰器的声明生成元数据。 你需要在命令行或 <code>tsconfig.json</code>里启用<code>emitDecoratorMetadata</code>编译器选项。</p>
<p><strong>Command Line</strong>:</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata</span><br></pre></td></tr></table></figure>

<p><strong>tsconfig.json</strong>:</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">"compilerOptions"</span>: &#123;</span><br><span class="line">        <span class="attr">"target"</span>: <span class="string">"ES5"</span>,</span><br><span class="line">        <span class="attr">"experimentalDecorators"</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="attr">"emitDecoratorMetadata"</span>: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当启用后，只要<code>reflect-metadata</code>库被引入了，设计阶段添加的类型信息可以在运行时使用。</p>
<p>如下例所示：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"reflect-metadata"</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Point &#123;</span><br><span class="line">    x: <span class="built_in">number</span>;</span><br><span class="line">    y: <span class="built_in">number</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> Line &#123;</span><br><span class="line">    <span class="keyword">private</span> _p0: Point;</span><br><span class="line">    <span class="keyword">private</span> _p1: Point;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@validate</span></span><br><span class="line">    <span class="keyword">set</span> p0(value: Point) &#123; <span class="keyword">this</span>._p0 = value; &#125;</span><br><span class="line">    <span class="keyword">get</span> p0() &#123; <span class="keyword">return</span> <span class="keyword">this</span>._p0; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@validate</span></span><br><span class="line">    <span class="keyword">set</span> p1(value: Point) &#123; <span class="keyword">this</span>._p1 = value; &#125;</span><br><span class="line">    <span class="keyword">get</span> p1() &#123; <span class="keyword">return</span> <span class="keyword">this</span>._p1; &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">validate</span>&lt;<span class="title">T</span>&gt;(<span class="params">target: <span class="built_in">any</span>, propertyKey: <span class="built_in">string</span>, descriptor: TypedPropertyDescriptor&lt;T&gt;</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">set</span> = descriptor.set;</span><br><span class="line">    descriptor.set = <span class="function"><span class="keyword">function</span> (<span class="params">value: T</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="keyword">type</span> = Reflect.getMetadata(<span class="string">"design:type"</span>, target, propertyKey);</span><br><span class="line">        <span class="keyword">if</span> (!(value <span class="keyword">instanceof</span> <span class="keyword">type</span>)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">"Invalid type."</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">set</span>(value);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>TypeScript编译器可以通过<code>@Reflect.metadata</code>装饰器注入设计阶段的类型信息。 你可以认为它相当于下面的TypeScript：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> Line &#123;</span><br><span class="line">    <span class="keyword">private</span> _p0: Point;</span><br><span class="line">    <span class="keyword">private</span> _p1: Point;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@validate</span></span><br><span class="line">    <span class="meta">@Reflect</span>.metadata(<span class="string">"design:type"</span>, Point)</span><br><span class="line">    <span class="keyword">set</span> p0(value: Point) &#123; <span class="keyword">this</span>._p0 = value; &#125;</span><br><span class="line">    <span class="keyword">get</span> p0() &#123; <span class="keyword">return</span> <span class="keyword">this</span>._p0; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@validate</span></span><br><span class="line">    <span class="meta">@Reflect</span>.metadata(<span class="string">"design:type"</span>, Point)</span><br><span class="line">    <span class="keyword">set</span> p1(value: Point) &#123; <span class="keyword">this</span>._p1 = value; &#125;</span><br><span class="line">    <span class="keyword">get</span> p1() &#123; <span class="keyword">return</span> <span class="keyword">this</span>._p1; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>注意  装饰器元数据是个实验性的特性并且可能在以后的版本中发生破坏性的改变（breaking changes）。</p>
</blockquote>

    </div>
    
    
    
    
    <div>
      
        <div>
    
        <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div>
    
</div>

      
    </div>
        <div class="reward-container">
  <div>坚持原创技术分享，您的支持将鼓励我继续创作！</div>
  <button onclick="var qr = document.getElementById('qr'); qr.style.display = (qr.style.display === 'none') ? 'block' : 'none';">
    打赏
  </button>
  <div id="qr" style="display: none;">
      
      <div style="display: inline-block;">
        <img src="/images/alipay.png" alt="Li Yudong 支付宝">
        <p>支付宝</p>
      </div>

  </div>
</div>

        

<div>
<ul class="post-copyright">
  <li class="post-copyright-author">
    <strong>本文作者： </strong>Li Yudong
  </li>
  <li class="post-copyright-link">
    <strong>本文链接：</strong>
    <a href="http://example.com/2021/12/01/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88%E5%88%9D%E5%AD%A6/typeScript%E5%88%9D%E5%AD%A6/" title="typeScript初学">http://example.com/2021/12/01/盒马技术栈初学/typeScript初学/</a>
  </li>
  <li class="post-copyright-license">
    <strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="noopener" target="_blank"><i class="fab fa-fw fa-creative-commons"></i>BY-NC-SA</a> 许可协议。转载请注明出处！
  </li>
</ul>
</div>

    
      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/%E5%85%AC%E5%8F%B8%E6%8A%80%E6%9C%AF%E6%A0%88/" rel="tag"># 公司技术栈</a>
          </div>
    
        

    
        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2021/11/30/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88%E5%88%9D%E5%AD%A6/Mobx%E5%85%A5%E9%97%A8/" rel="prev" title="Mobx入门">
      <i class="fa fa-chevron-left"></i> Mobx入门
    </a></div>
      <div class="post-nav-item">
    <a href="/2021/12/02/%E7%9B%92%E9%A9%AC%E6%8A%80%E6%9C%AF%E6%A0%88%E5%88%9D%E5%AD%A6/typeScript%E5%A3%B0%E6%98%8E%E6%96%87%E4%BB%B6/" rel="next" title="TypeScript声明文件">
      TypeScript声明文件 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#基础指南"><span class="nav-number">1.</span> <span class="nav-text">基础指南</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#基础类型"><span class="nav-number">1.1.</span> <span class="nav-text">基础类型</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#元组-Tuple"><span class="nav-number">1.1.1.</span> <span class="nav-text">元组 Tuple</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#枚举"><span class="nav-number">1.1.2.</span> <span class="nav-text">枚举</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Any"><span class="nav-number">1.1.3.</span> <span class="nav-text">Any</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Void"><span class="nav-number">1.1.4.</span> <span class="nav-text">Void</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Never"><span class="nav-number">1.1.5.</span> <span class="nav-text">Never</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Object"><span class="nav-number">1.1.6.</span> <span class="nav-text">Object</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#类型断言"><span class="nav-number">1.1.7.</span> <span class="nav-text">类型断言</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#接口"><span class="nav-number">1.2.</span> <span class="nav-text">接口</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#接口初探"><span class="nav-number">1.2.1.</span> <span class="nav-text">接口初探</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#可选属性"><span class="nav-number">1.2.2.</span> <span class="nav-text">可选属性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#只读属性"><span class="nav-number">1.2.3.</span> <span class="nav-text">只读属性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#额外的属性检查"><span class="nav-number">1.2.4.</span> <span class="nav-text">额外的属性检查</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#函数类型"><span class="nav-number">1.2.5.</span> <span class="nav-text">函数类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#可索引的类型"><span class="nav-number">1.2.6.</span> <span class="nav-text">可索引的类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#类类型"><span class="nav-number">1.2.7.</span> <span class="nav-text">类类型</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#实现接口"><span class="nav-number">1.2.7.1.</span> <span class="nav-text">实现接口</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#静态与实例部分"><span class="nav-number">1.2.7.2.</span> <span class="nav-text">静态与实例部分</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#继承接口"><span class="nav-number">1.2.8.</span> <span class="nav-text">继承接口</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#混合类型"><span class="nav-number">1.2.9.</span> <span class="nav-text">混合类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#接口继承类"><span class="nav-number">1.2.10.</span> <span class="nav-text">接口继承类</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#类"><span class="nav-number">1.3.</span> <span class="nav-text">类</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#继承"><span class="nav-number">1.3.1.</span> <span class="nav-text">继承</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#公共，私有与受保护的修饰符"><span class="nav-number">1.3.2.</span> <span class="nav-text">公共，私有与受保护的修饰符</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#readonly修饰符"><span class="nav-number">1.3.3.</span> <span class="nav-text">readonly修饰符</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#参数属性"><span class="nav-number">1.3.4.</span> <span class="nav-text">参数属性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#存取器"><span class="nav-number">1.3.5.</span> <span class="nav-text">存取器</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#静态属性"><span class="nav-number">1.3.6.</span> <span class="nav-text">静态属性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#抽象类"><span class="nav-number">1.3.7.</span> <span class="nav-text">抽象类</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#高级技巧"><span class="nav-number">1.3.8.</span> <span class="nav-text">高级技巧</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#构造函数"><span class="nav-number">1.3.8.1.</span> <span class="nav-text">构造函数</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#把类当做接口使用"><span class="nav-number">1.3.8.2.</span> <span class="nav-text">把类当做接口使用</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#函数"><span class="nav-number">1.4.</span> <span class="nav-text">函数</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#函数类型-1"><span class="nav-number">1.4.1.</span> <span class="nav-text">函数类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#可选参数和默认参数"><span class="nav-number">1.4.2.</span> <span class="nav-text">可选参数和默认参数</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#剩余参数"><span class="nav-number">1.4.3.</span> <span class="nav-text">剩余参数</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#this"><span class="nav-number">1.5.</span> <span class="nav-text">this</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#this和箭头函数"><span class="nav-number">1.5.1.</span> <span class="nav-text">this和箭头函数</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#this参数"><span class="nav-number">1.5.2.</span> <span class="nav-text">this参数</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#重载"><span class="nav-number">1.5.3.</span> <span class="nav-text">重载</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#泛型"><span class="nav-number">1.6.</span> <span class="nav-text">泛型</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#泛型之Hello-World"><span class="nav-number">1.6.1.</span> <span class="nav-text">泛型之Hello World</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#使用泛型变量"><span class="nav-number">1.6.2.</span> <span class="nav-text">使用泛型变量</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#泛型类型"><span class="nav-number">1.6.3.</span> <span class="nav-text">泛型类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#泛型类"><span class="nav-number">1.6.4.</span> <span class="nav-text">泛型类</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#泛型约束"><span class="nav-number">1.6.5.</span> <span class="nav-text">泛型约束</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#枚举-1"><span class="nav-number">1.7.</span> <span class="nav-text">枚举</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#计算和常量成员"><span class="nav-number">1.7.1.</span> <span class="nav-text">计算和常量成员</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#联合枚举与枚举成员的类型"><span class="nav-number">1.7.2.</span> <span class="nav-text">联合枚举与枚举成员的类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#运行时的枚举"><span class="nav-number">1.7.3.</span> <span class="nav-text">运行时的枚举</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#反向映射"><span class="nav-number">1.7.4.</span> <span class="nav-text">反向映射</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#外部枚举"><span class="nav-number">1.7.5.</span> <span class="nav-text">外部枚举</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#类型推论与兼容"><span class="nav-number">1.8.</span> <span class="nav-text">类型推论与兼容</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#类型推论"><span class="nav-number">1.8.1.</span> <span class="nav-text">类型推论</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#最佳通用类型"><span class="nav-number">1.8.1.1.</span> <span class="nav-text">最佳通用类型</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#上下文类型"><span class="nav-number">1.8.1.2.</span> <span class="nav-text">上下文类型</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#类型兼容性"><span class="nav-number">1.8.2.</span> <span class="nav-text">类型兼容性</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#可靠性"><span class="nav-number">1.8.2.1.</span> <span class="nav-text">可靠性</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#比较两个函数"><span class="nav-number">1.8.2.2.</span> <span class="nav-text">比较两个函数</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#枚举-2"><span class="nav-number">1.8.2.3.</span> <span class="nav-text">枚举</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#类-1"><span class="nav-number">1.8.2.4.</span> <span class="nav-text">类</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#泛型-1"><span class="nav-number">1.8.3.</span> <span class="nav-text">泛型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#高级主题"><span class="nav-number">1.8.4.</span> <span class="nav-text">高级主题</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#高级类型"><span class="nav-number">1.9.</span> <span class="nav-text">高级类型</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#交叉类型"><span class="nav-number">1.9.1.</span> <span class="nav-text">交叉类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#联合类型"><span class="nav-number">1.9.2.</span> <span class="nav-text">联合类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#类型保护与区分"><span class="nav-number">1.9.3.</span> <span class="nav-text">类型保护与区分</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#可以为null的类型"><span class="nav-number">1.9.4.</span> <span class="nav-text">可以为null的类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#类型别名"><span class="nav-number">1.9.5.</span> <span class="nav-text">类型别名</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#可辨识联合"><span class="nav-number">1.9.6.</span> <span class="nav-text">可辨识联合</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#多态的-this类型"><span class="nav-number">1.9.7.</span> <span class="nav-text">多态的 this类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#索引类型"><span class="nav-number">1.9.8.</span> <span class="nav-text">索引类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#映射类型"><span class="nav-number">1.9.9.</span> <span class="nav-text">映射类型</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Symbols"><span class="nav-number">1.10.</span> <span class="nav-text">Symbols</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#众所周知的Symbols"><span class="nav-number">1.10.1.</span> <span class="nav-text">众所周知的Symbols</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#迭代器和生成器"><span class="nav-number">1.11.</span> <span class="nav-text">迭代器和生成器</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#代码生成"><span class="nav-number">1.11.1.</span> <span class="nav-text">代码生成</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#模块"><span class="nav-number">1.12.</span> <span class="nav-text">模块</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#导出"><span class="nav-number">1.12.1.</span> <span class="nav-text">导出</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#导入"><span class="nav-number">1.12.2.</span> <span class="nav-text">导入</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#默认导出"><span class="nav-number">1.12.3.</span> <span class="nav-text">默认导出</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#生成模块代码"><span class="nav-number">1.12.4.</span> <span class="nav-text">生成模块代码</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#简单示例"><span class="nav-number">1.12.5.</span> <span class="nav-text">简单示例</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#可选的模块加载和其它高级加载场景"><span class="nav-number">1.12.5.1.</span> <span class="nav-text">可选的模块加载和其它高级加载场景</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#其它的JavaScript库"><span class="nav-number">1.12.6.</span> <span class="nav-text">其它的JavaScript库</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#外部模块"><span class="nav-number">1.12.6.1.</span> <span class="nav-text">外部模块</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#UMD模块"><span class="nav-number">1.12.6.2.</span> <span class="nav-text">UMD模块</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#创建模块结构指导"><span class="nav-number">1.12.7.</span> <span class="nav-text">创建模块结构指导</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#使用重新导出进行扩展"><span class="nav-number">1.12.8.</span> <span class="nav-text">使用重新导出进行扩展</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#危险信号"><span class="nav-number">1.12.9.</span> <span class="nav-text">危险信号</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#命名空间"><span class="nav-number">1.13.</span> <span class="nav-text">命名空间</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#命名空间-1"><span class="nav-number">1.13.1.</span> <span class="nav-text">命名空间</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#分离到多文件"><span class="nav-number">1.13.2.</span> <span class="nav-text">分离到多文件</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#别名"><span class="nav-number">1.13.3.</span> <span class="nav-text">别名</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#使用其它的JavaScript库"><span class="nav-number">1.13.4.</span> <span class="nav-text">使用其它的JavaScript库</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#命名空间与模块"><span class="nav-number">1.14.</span> <span class="nav-text">命名空间与模块</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#命名空间和模块的陷阱"><span class="nav-number">1.14.1.</span> <span class="nav-text">命名空间和模块的陷阱</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#模块的取舍"><span class="nav-number">1.14.2.</span> <span class="nav-text">模块的取舍</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#模块解析"><span class="nav-number">1.15.</span> <span class="nav-text">模块解析</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#模块解析策略"><span class="nav-number">1.15.1.</span> <span class="nav-text">模块解析策略</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#TypeScript如何解析模块"><span class="nav-number">1.15.1.1.</span> <span class="nav-text">TypeScript如何解析模块</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#附加的模块解析标记"><span class="nav-number">1.15.1.2.</span> <span class="nav-text">附加的模块解析标记</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#跟踪模块解析"><span class="nav-number">1.15.2.</span> <span class="nav-text">跟踪模块解析</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#常见问题"><span class="nav-number">1.15.3.</span> <span class="nav-text">常见问题</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#声明合并"><span class="nav-number">1.16.</span> <span class="nav-text">声明合并</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#合并接口"><span class="nav-number">1.16.1.</span> <span class="nav-text">合并接口</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#合并命名空间"><span class="nav-number">1.16.2.</span> <span class="nav-text">合并命名空间</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#命名空间与类和函数和枚举类型合并"><span class="nav-number">1.16.3.</span> <span class="nav-text">命名空间与类和函数和枚举类型合并</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#合并命名空间和类"><span class="nav-number">1.16.3.1.</span> <span class="nav-text">合并命名空间和类</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#非法的合并"><span class="nav-number">1.16.3.2.</span> <span class="nav-text">非法的合并</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#模块扩展"><span class="nav-number">1.17.</span> <span class="nav-text">模块扩展</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#全局扩展"><span class="nav-number">1.17.1.</span> <span class="nav-text">全局扩展</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#JSX"><span class="nav-number">1.18.</span> <span class="nav-text">JSX</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#固有元素"><span class="nav-number">1.18.1.</span> <span class="nav-text">固有元素</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#基于值的元素"><span class="nav-number">1.18.2.</span> <span class="nav-text">基于值的元素</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#无状态函数组件"><span class="nav-number">1.18.3.</span> <span class="nav-text">无状态函数组件</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#类组件"><span class="nav-number">1.18.4.</span> <span class="nav-text">类组件</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#JSX结果类型"><span class="nav-number">1.18.5.</span> <span class="nav-text">JSX结果类型</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#React整合"><span class="nav-number">1.18.6.</span> <span class="nav-text">React整合</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#工厂函数"><span class="nav-number">1.18.7.</span> <span class="nav-text">工厂函数</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#装饰器"><span class="nav-number">1.19.</span> <span class="nav-text">装饰器</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#装饰器工厂"><span class="nav-number">1.19.1.</span> <span class="nav-text">装饰器工厂</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#装饰器组合"><span class="nav-number">1.19.2.</span> <span class="nav-text">装饰器组合</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#装饰器求值"><span class="nav-number">1.19.3.</span> <span class="nav-text">装饰器求值</span></a></li></ol></li></ol></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="Li Yudong"
      src="/images/author.jpg">
  <p class="site-author-name" itemprop="name">Li Yudong</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">95</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">14</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">18</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/tiarmor1" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;tiarmor1" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:1157019137@qq.com" title="E-Mail → mailto:1157019137@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>
  <div class="cc-license motion-element" itemprop="license">
    <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" class="cc-opacity" rel="noopener" target="_blank"><img src="/images/cc-by-nc-sa.svg" alt="Creative Commons"></a>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 2020 – 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Li Yudong</span>
</div>

        








      </div>
    </footer>
  </div>

  
  
  <script color='0,0,0' opacity='0.5' zIndex='-1' count='150' src="/lib1/canvas-nest/canvas-nest.min.js"></script>
  <script src="/lib1/anime.min.js"></script>
  <script src="/lib1/velocity/velocity.min.js"></script>
  <script src="/lib1/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  




  
<script src="/js/local-search.js"></script>













  

  

  
   <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas> 
   <script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script> 
   <script type="text/javascript" src="/js/fireworks.js"></script>
  
</body>
</html>

